refactor(reallife_asset): uapi网关架构重构 + UI全面修复

架构变更:
- 废弃volcengine_client.py直连,改为通过Sage uapi网关调用供应商API
- rl_vendor_config表新增upappid和api_mapping(JSON)字段
- 新增_call_vendor()统一路由: vendor→upappid→apiname→UpAppApi.call()
- 支持多供应商灵活映射,各家API数量/逻辑不同通过api_mapping配置

管理端UI:
- vendor_config_manage.ui: Tabular列表展示供应商配置
- vendor_config_edit.ui: 供应商配置编辑页(AK/SK通过upapp/upappkey管理)
- org_group_manage.ui: 机构映射管理页
- 新增api/get_upapp_list.dspy获取上位系统下拉选项
- 新增api/get_status_list.dspy获取状态下拉选项

客户端UI:
- create_validate.ui: 真人认证页面,支持选择供应商创建H5认证
- upload_asset.ui: 上传素材页面,支持URL/base64上传
- index.ui: 新增客户端入口卡片(真人认证、上传素材)
- 所有Form字段使用正确uitype(code/str/text),确保可输入

清理:
- 废弃rl_app_user表,统一使用rl_asset_group+rl_org_group
- 简化API签名,去除冗余apikey/secretkey透传
This commit is contained in:
yumoqing 2026-05-28 18:49:46 +08:00
parent 1d05b7e36b
commit a21eabbb11
31 changed files with 1108 additions and 519 deletions

View File

@ -11,7 +11,8 @@
"vendor_group_id": {"title": "供应商组合ID", "widgettype": "Text"}, "vendor_group_id": {"title": "供应商组合ID", "widgettype": "Text"},
"local_group_id": {"title": "本地组合ID", "widgettype": "Text"}, "local_group_id": {"title": "本地组合ID", "widgettype": "Text"},
"status": {"title": "状态", "widgettype": "Text"}, "status": {"title": "状态", "widgettype": "Text"},
"create_time": {"title": "创建时间", "widgettype": "Text"} "create_time": {"title": "创建时间", "widgettype": "Text"},
"update_time": {"title": "更新时间", "widgettype": "Text"}
}, },
"editable": { "editable": {
"new_data_url": "{{entire_url('../api/rl_org_group_create.dspy')}}", "new_data_url": "{{entire_url('../api/rl_org_group_create.dspy')}}",

View File

@ -3,10 +3,14 @@
"alias": "rl_vendor_config_list", "alias": "rl_vendor_config_list",
"title": "供应商配置管理", "title": "供应商配置管理",
"params": { "params": {
"sortby": ["create_time desc"],
"logined_userorgid": "org_id", "logined_userorgid": "org_id",
"browserfields": { "browserfields": {
"id": {"title": "ID", "widgettype": "Text"}, "id": {"title": "ID", "widgettype": "Text"},
"vendor": {"title": "供应商", "widgettype": "Text"}, "vendor": {"title": "供应商标识", "widgettype": "Text"},
"vendor_title": {"title": "供应商名称", "widgettype": "Text"},
"upappid": {"title": "上位系统ID", "widgettype": "Text"},
"api_mapping": {"title": "API映射", "widgettype": "Text"},
"status": {"title": "状态", "widgettype": "Text"}, "status": {"title": "状态", "widgettype": "Text"},
"callback_url": {"title": "回调URL", "widgettype": "Text"}, "callback_url": {"title": "回调URL", "widgettype": "Text"},
"create_time": {"title": "创建时间", "widgettype": "Text"}, "create_time": {"title": "创建时间", "widgettype": "Text"},

View File

@ -51,6 +51,11 @@
"name": "create_time", "name": "create_time",
"title": "创建时间", "title": "创建时间",
"type": "datetime" "type": "datetime"
},
{
"name": "update_time",
"title": "更新时间",
"type": "datetime"
} }
], ],
"indexes": [ "indexes": [

View File

@ -16,20 +16,29 @@
}, },
{ {
"name": "vendor", "name": "vendor",
"title": "供应商", "title": "供应商标识",
"type": "str", "type": "str",
"length": 50, "length": 50,
"nullable": "no" "nullable": "no"
}, },
{ {
"name": "ak", "name": "vendor_title",
"title": "AccessKey(加密)", "title": "供应商名称",
"type": "text" "type": "str",
"length": 100
}, },
{ {
"name": "sk", "name": "upappid",
"title": "SecretKey(加密)", "title": "上位系统ID(uapi)",
"type": "text" "type": "str",
"length": 32,
"nullable": "no"
},
{
"name": "api_mapping",
"title": "API映射(JSON)",
"type": "text",
"nullable": "no"
}, },
{ {
"name": "status", "name": "status",
@ -62,13 +71,5 @@
"idxfields": ["vendor"] "idxfields": ["vendor"]
} }
], ],
"codes": [ "codes": []
{
"field": "vendor",
"table": "appcodes_kv",
"valuefield": "k",
"textfield": "v",
"cond": "parentid='rl_vendor'"
}
]
} }

View File

@ -1,7 +1,8 @@
""" """
reallife_asset module - Real Person Portrait Asset Management. reallife_asset module - Real Person Portrait Asset Management.
Supports multiple vendors (Volcengine Ark, etc.) for managing Supports multiple vendors via Sage uapi gateway.
real person portrait asset groups and assets. Vendor API routing: rl_vendor_config.api_mapping JSON maps internal
operations to uapi apinames. Each vendor has its own upappid.
""" """
import json import json
from datetime import datetime from datetime import datetime
@ -12,8 +13,7 @@ from ahserver.serverenv import ServerEnv
from appPublic.log import debug, exception, error from appPublic.log import debug, exception, error
from appPublic.uniqueID import getID from appPublic.uniqueID import getID
from appPublic.dictObject import DictObject from appPublic.dictObject import DictObject
from uapi.uapi import UpAppApi
from .volcengine_client import get_vendor_client
MODULE_NAME = "reallife_asset" MODULE_NAME = "reallife_asset"
@ -23,28 +23,88 @@ def _get_dbname():
return f(MODULE_NAME) return f(MODULE_NAME)
def _get_client(vendor, apikey, secretkey): async def _get_vendor_config(vendor):
"""Get vendor API client.""" """Look up vendor config: upappid + api_mapping from rl_vendor_config."""
return get_vendor_client(vendor, apikey, secretkey) dbname = _get_dbname()
db = DBPools()
async with db.sqlorContext(dbname) as sor:
recs = await sor.R("rl_vendor_config", {"vendor": vendor})
if not recs:
return {"success": False, "message": f"供应商配置不存在: {vendor}"}
rec = recs[0]
if rec.status != "active":
return {"success": False, "message": f"供应商服务已停用: {vendor}"}
try:
api_mapping = json.loads(rec.api_mapping) if rec.api_mapping else {}
except (json.JSONDecodeError, TypeError):
api_mapping = {}
return {
"success": True,
"upappid": rec.upappid,
"api_mapping": api_mapping,
"callback_url": getattr(rec, "callback_url", ""),
}
def _get_apiname(api_mapping, operation):
"""Get uapi apiname for an internal operation."""
apiname = api_mapping.get(operation)
if not apiname:
return None
return apiname
async def _call_vendor(vendor, operation, params={}):
"""
Call vendor API via uapi gateway.
Looks up upappid + apiname from rl_vendor_config, then calls UpAppApi.
Returns parsed dict from response.
"""
cfg = await _get_vendor_config(vendor)
if not cfg.get("success"):
return cfg
upappid = cfg["upappid"]
apiname = _get_apiname(cfg["api_mapping"], operation)
if not apiname:
return {
"success": False,
"message": f"供应商 {vendor} 未配置操作 {operation}",
}
# callerid = vendor name, used to look up API key in upappkey table
callerid = vendor
try:
uapi = UpAppApi()
raw = await uapi.call(upappid, apiname, callerid, params=params)
if isinstance(raw, bytes):
raw = raw.decode("utf-8")
result = json.loads(raw) if raw else {}
return result
except Exception as e:
error(f"_call_vendor {vendor}/{operation} error: {e}\\n{format_exc()}")
return {"error": str(e)}
# ============================================================ # ============================================================
# Asset Group operations # Asset Group operations (admin-side, uses vendor config)
# ============================================================ # ============================================================
async def rl_create_validate_session(org_id, vendor, callback_url, async def rl_create_validate_session(org_id, vendor, callback_url,
project_name="default", project_name="default",
apikey=None, secretkey=None,
user_id=None): user_id=None):
"""Create H5 verification session for real person auth.""" """Create H5 verification session for real person auth."""
client = _get_client(vendor, apikey, secretkey) params = {
result = client.create_visual_validate_session(callback_url, project_name) "CallbackURL": callback_url or "",
"ProjectName": project_name,
}
result = await _call_vendor(vendor, "create_session", params)
if "error" in result: if "error" in result or "Error" in result:
return {"success": False, "message": result.get("error", "API调用失败")} return {"success": False, "message": result.get("error", result.get("Message", "API调用失败"))}
byted_token = result.get("BytedToken", "") byted_token = result.get("BytedToken", result.get("Result", {}).get("BytedToken", ""))
h5_link = result.get("H5Link", "") h5_link = result.get("H5Link", result.get("Result", {}).get("H5Link", ""))
# Save to local DB # Save to local DB
dbname = _get_dbname() dbname = _get_dbname()
@ -63,7 +123,7 @@ async def rl_create_validate_session(org_id, vendor, callback_url,
"status": "pending", "status": "pending",
"byted_token": byted_token, "byted_token": byted_token,
"h5_link": h5_link, "h5_link": h5_link,
"callback_url": callback_url, "callback_url": callback_url or "",
"created_by": user_id or "", "created_by": user_id or "",
"create_time": now, "create_time": now,
"update_time": now, "update_time": now,
@ -76,8 +136,7 @@ async def rl_create_validate_session(org_id, vendor, callback_url,
} }
async def rl_check_validate_result(local_group_id, vendor, async def rl_check_validate_result(local_group_id, vendor, project_name="default"):
apikey=None, secretkey=None):
"""Check real person validation result and get vendor Group ID.""" """Check real person validation result and get vendor Group ID."""
dbname = _get_dbname() dbname = _get_dbname()
db = DBPools() db = DBPools()
@ -87,15 +146,18 @@ async def rl_check_validate_result(local_group_id, vendor,
return {"success": False, "message": "本地记录不存在"} return {"success": False, "message": "本地记录不存在"}
rec = recs[0] rec = recs[0]
byted_token = rec.byted_token byted_token = rec.byted_token
project_name = rec.project_name or "default"
client = _get_client(vendor, apikey, secretkey) params = {
result = client.get_visual_validate_result(byted_token, project_name) "BytedToken": byted_token,
"ProjectName": project_name,
}
result = await _call_vendor(vendor, "check_session", params)
if "error" in result: if "error" in result or "Error" in result:
return {"success": False, "message": result.get("error", "查询失败")} return {"success": False, "message": result.get("error", result.get("Message", "查询失败"))}
vendor_group_id = result.get("GroupId", "") r = result.get("Result", result)
vendor_group_id = r.get("GroupId", result.get("GroupId", ""))
if not vendor_group_id: if not vendor_group_id:
return {"success": False, "message": "尚未完成认证或认证失败"} return {"success": False, "message": "尚未完成认证或认证失败"}
@ -113,35 +175,36 @@ async def rl_check_validate_result(local_group_id, vendor,
return {"success": True, "vendor_group_id": vendor_group_id} return {"success": True, "vendor_group_id": vendor_group_id}
async def rl_create_asset(org_id, local_group_id, source_url, async def rl_create_asset(org_id, vendor_group_id, source_url,
asset_type="Image", name="", asset_type="Image", name="",
vendor=None, apikey=None, secretkey=None, vendor=None, project_name="default",
user_id=None): user_id=None):
"""Upload asset to vendor and create local record.""" """Upload asset to vendor and create local record."""
dbname = _get_dbname() dbname = _get_dbname()
db = DBPools() db = DBPools()
# Find the group to get vendor and local_group_id
async with db.sqlorContext(dbname) as sor: async with db.sqlorContext(dbname) as sor:
recs = await sor.R("rl_asset_group", {"id": local_group_id}) recs = await sor.R("rl_asset_group", {"vendor_group_id": vendor_group_id})
if not recs: if not recs:
return {"success": False, "message": "素材组合不存在"} return {"success": False, "message": "素材组合不存在"}
grp = recs[0] grp = recs[0]
vendor = vendor or grp.vendor vendor = vendor or grp.vendor
vendor_group_id = grp.vendor_group_id local_group_id = grp.id
project_name = grp.project_name or "default" project_name = grp.project_name or project_name
if not vendor_group_id: # Call vendor API
return {"success": False, "message": "素材组合尚未完成真人认证"} params = {
"GroupId": vendor_group_id,
"URL": source_url,
"AssetType": asset_type,
"ProjectName": project_name,
}
if name:
params["Name"] = name
result = await _call_vendor(vendor, "upload_asset", params)
client = _get_client(vendor, apikey, secretkey) vendor_asset_id = result.get("Id", result.get("Result", {}).get("Id", ""))
result = client.create_asset(
vendor_group_id, source_url, asset_type, name, project_name
)
vendor_asset_id = result.get("Id", "")
if not vendor_asset_id and "error" not in result:
# Try nested result structure
r = result.get("Result", {})
vendor_asset_id = r.get("Id", "")
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S") now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
asset_id = getID() asset_id = getID()
@ -166,17 +229,17 @@ async def rl_create_asset(org_id, local_group_id, source_url,
"update_time": now, "update_time": now,
}) })
has_error = "error" in result or "Error" in result
return { return {
"success": "error" not in result, "success": not has_error,
"id": asset_id, "id": asset_id,
"vendor_asset_id": vendor_asset_id, "vendor_asset_id": vendor_asset_id,
"status": "Processing", "status": "Processing",
"message": result.get("error", ""), "message": result.get("error", result.get("Message", "")),
} }
async def rl_sync_asset_status(asset_id, vendor=None, async def rl_sync_asset_status(asset_id):
apikey=None, secretkey=None):
"""Sync asset status from vendor.""" """Sync asset status from vendor."""
dbname = _get_dbname() dbname = _get_dbname()
db = DBPools() db = DBPools()
@ -185,25 +248,25 @@ async def rl_sync_asset_status(asset_id, vendor=None,
if not recs: if not recs:
return {"success": False, "message": "素材不存在"} return {"success": False, "message": "素材不存在"}
rec = recs[0] rec = recs[0]
vendor = vendor or rec.vendor vendor = rec.vendor
vendor_asset_id = rec.vendor_asset_id vendor_asset_id = rec.vendor_asset_id
project_name = rec.project_name or "default" project_name = rec.project_name or "default"
if not vendor_asset_id: if not vendor_asset_id:
return {"success": False, "message": "无供应商端资产ID"} return {"success": False, "message": "无供应商端资产ID"}
client = _get_client(vendor, apikey, secretkey) params = {
result = client.get_asset(vendor_asset_id, project_name) "Id": vendor_asset_id,
"ProjectName": project_name,
}
result = await _call_vendor(vendor, "get_asset", params)
if "error" in result: if "error" in result or "Error" in result:
return {"success": False, "message": result.get("error", "查询失败")} return {"success": False, "message": result.get("error", result.get("Message", "查询失败"))}
# Extract status from result (may be nested under Result) r = result.get("Result", result)
status = result.get("Status", "") status = r.get("Status", result.get("Status", ""))
if not status: url = r.get("URL", result.get("URL", ""))
r = result.get("Result", {})
status = r.get("Status", result.get("status", ""))
url = result.get("URL", result.get("Result", {}).get("URL", ""))
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S") now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
async with db.sqlorContext(dbname) as sor: async with db.sqlorContext(dbname) as sor:
@ -219,8 +282,7 @@ async def rl_sync_asset_status(asset_id, vendor=None,
return {"success": True, "status": status, "url": url} return {"success": True, "status": status, "url": url}
async def rl_delete_asset(asset_id, vendor=None, async def rl_delete_asset(asset_id):
apikey=None, secretkey=None):
"""Delete asset from vendor and local DB.""" """Delete asset from vendor and local DB."""
dbname = _get_dbname() dbname = _get_dbname()
db = DBPools() db = DBPools()
@ -229,15 +291,14 @@ async def rl_delete_asset(asset_id, vendor=None,
if not recs: if not recs:
return {"success": False, "message": "素材不存在"} return {"success": False, "message": "素材不存在"}
rec = recs[0] rec = recs[0]
vendor = vendor or rec.vendor vendor = rec.vendor
vendor_asset_id = rec.vendor_asset_id vendor_asset_id = rec.vendor_asset_id
project_name = rec.project_name or "default" project_name = rec.project_name or "default"
# Delete from vendor # Delete from vendor
if vendor_asset_id: if vendor_asset_id:
client = _get_client(vendor, apikey, secretkey) params = {"Id": vendor_asset_id, "ProjectName": project_name}
result = client.delete_asset(vendor_asset_id, project_name) await _call_vendor(vendor, "delete_asset", params)
debug(f"vendor delete asset: {result}")
# Delete local # Delete local
async with db.sqlorContext(dbname) as sor: async with db.sqlorContext(dbname) as sor:
@ -246,8 +307,7 @@ async def rl_delete_asset(asset_id, vendor=None,
return {"success": True} return {"success": True}
async def rl_delete_group(local_group_id, vendor=None, async def rl_delete_group(local_group_id):
apikey=None, secretkey=None):
"""Delete asset group from vendor and local DB (cascade).""" """Delete asset group from vendor and local DB (cascade)."""
dbname = _get_dbname() dbname = _get_dbname()
db = DBPools() db = DBPools()
@ -256,15 +316,14 @@ async def rl_delete_group(local_group_id, vendor=None,
if not recs: if not recs:
return {"success": False, "message": "素材组合不存在"} return {"success": False, "message": "素材组合不存在"}
rec = recs[0] rec = recs[0]
vendor = vendor or rec.vendor vendor = rec.vendor
vendor_group_id = rec.vendor_group_id vendor_group_id = rec.vendor_group_id
project_name = rec.project_name or "default" project_name = rec.project_name or "default"
# Delete from vendor # Delete from vendor
if vendor_group_id: if vendor_group_id:
client = _get_client(vendor, apikey, secretkey) params = {"Id": vendor_group_id, "ProjectName": project_name}
result = client.delete_asset_group(vendor_group_id, project_name) await _call_vendor(vendor, "delete_group", params)
debug(f"vendor delete group: {result}")
# Delete local (cascade) # Delete local (cascade)
async with db.sqlorContext(dbname) as sor: async with db.sqlorContext(dbname) as sor:
@ -274,12 +333,14 @@ async def rl_delete_group(local_group_id, vendor=None,
return {"success": True} return {"success": True}
async def rl_sync_group_from_vendor(org_id, vendor, async def rl_sync_group_from_vendor(org_id, vendor, project_name="default"):
apikey=None, secretkey=None,
project_name="default"):
"""Sync asset groups from vendor to local DB.""" """Sync asset groups from vendor to local DB."""
client = _get_client(vendor, apikey, secretkey) params = {
result = client.list_asset_groups(project_name=project_name) "Filter": {"GroupType": "LivenessFace"},
"PageNumber": 1,
"PageSize": 100,
}
result = await _call_vendor(vendor, "list_groups", params)
items = result.get("Items", result.get("Result", {}).get("Items", [])) items = result.get("Items", result.get("Result", {}).get("Items", []))
synced = 0 synced = 0
@ -291,7 +352,6 @@ async def rl_sync_group_from_vendor(org_id, vendor,
vgid = item.get("Id", "") vgid = item.get("Id", "")
if not vgid: if not vgid:
continue continue
# Check if exists
existing = await sor.R("rl_asset_group", { existing = await sor.R("rl_asset_group", {
"vendor": vendor, "vendor": vendor,
"vendor_group_id": vgid, "vendor_group_id": vgid,
@ -325,8 +385,7 @@ async def rl_sync_group_from_vendor(org_id, vendor,
return {"success": True, "synced": synced} return {"success": True, "synced": synced}
async def rl_sync_assets_from_vendor(org_id, local_group_id, async def rl_sync_assets_from_vendor(org_id, local_group_id):
vendor=None, apikey=None, secretkey=None):
"""Sync assets for a group from vendor to local DB.""" """Sync assets for a group from vendor to local DB."""
dbname = _get_dbname() dbname = _get_dbname()
db = DBPools() db = DBPools()
@ -335,15 +394,19 @@ async def rl_sync_assets_from_vendor(org_id, local_group_id,
if not recs: if not recs:
return {"success": False, "message": "素材组合不存在"} return {"success": False, "message": "素材组合不存在"}
grp = recs[0] grp = recs[0]
vendor = vendor or grp.vendor vendor = grp.vendor
vendor_group_id = grp.vendor_group_id vendor_group_id = grp.vendor_group_id
project_name = grp.project_name or "default" project_name = grp.project_name or "default"
if not vendor_group_id: if not vendor_group_id:
return {"success": False, "message": "无供应商端组合ID"} return {"success": False, "message": "无供应商端组合ID"}
client = _get_client(vendor, apikey, secretkey) params = {
result = client.list_assets(group_ids=[vendor_group_id]) "Filter": {"GroupType": "LivenessFace", "GroupIds": [vendor_group_id]},
"PageNumber": 1,
"PageSize": 100,
}
result = await _call_vendor(vendor, "list_assets", params)
items = result.get("Items", result.get("Result", {}).get("Items", [])) items = result.get("Items", result.get("Result", {}).get("Items", []))
synced = 0 synced = 0
@ -388,47 +451,28 @@ async def rl_sync_assets_from_vendor(org_id, local_group_id,
# ============================================================ # ============================================================
# Downapp User API Proxies # Downapp User API Proxies (client-facing via dapi Bearer auth)
# ============================================================ # ============================================================
async def _get_vendor_keys(vendor="volcengine"): async def rl_verify_user(org_id, user_id, vendor, project_name="default"):
"""Helper: Get vendor AK/SK from config table.""" """User proxy: Create H5 verification session via uapi gateway."""
dbname = _get_dbname() callback_cfg = await _get_vendor_config(vendor)
db = DBPools() if not callback_cfg.get("success"):
async with db.sqlorContext(dbname) as sor: return callback_cfg
recs = await sor.R("rl_vendor_config", {"vendor": vendor}) callback_url = callback_cfg.get("callback_url", "")
if not recs:
return {"success": False, "message": "供应商配置不存在"}
rec = recs[0]
if rec.status != "active":
return {"success": False, "message": f"供应商服务已停用"}
env = ServerEnv()
ak = env.password_decode(rec.ak)
sk = env.password_decode(rec.sk)
return {"success": True, "ak": ak, "sk": sk, "callback_url": rec.callback_url}
params = {
"CallbackURL": callback_url,
"ProjectName": project_name,
}
result = await _call_vendor(vendor, "create_session", params)
async def rl_verify_user(org_id, user_id, project_name="default"): if "error" in result or "Error" in result:
"""User proxy: Verify vendor config -> Call vendor -> Save org-group mapping.""" return {"success": False, "message": result.get("error", result.get("Message", "API调用失败"))}
keys = await _get_vendor_keys()
if not keys.get("success"):
return keys
# Call Vendor API
client = _get_client("volcengine", keys["ak"], keys["sk"])
callback_url = keys.get("callback_url", "")
result = client.create_visual_validate_session(callback_url, project_name)
if "error" in result:
return {"success": False, "message": result.get("error", "API调用失败")}
byted_token = result.get("BytedToken", "") byted_token = result.get("BytedToken", result.get("Result", {}).get("BytedToken", ""))
h5_link = result.get("H5Link", "") h5_link = result.get("H5Link", result.get("Result", {}).get("H5Link", ""))
# Save local record for checking status later
# We use rl_asset_group for temporary state tracking
gid = getID() gid = getID()
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S") now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
db = DBPools() db = DBPools()
@ -436,7 +480,7 @@ async def rl_verify_user(org_id, user_id, project_name="default"):
await sor.I("rl_asset_group", { await sor.I("rl_asset_group", {
"id": gid, "id": gid,
"org_id": org_id, "org_id": org_id,
"vendor": "volcengine", "vendor": vendor,
"name": f"待认证-{user_id}", "name": f"待认证-{user_id}",
"title": f"待认证", "title": f"待认证",
"group_type": "LivenessFace", "group_type": "LivenessFace",
@ -458,73 +502,12 @@ async def rl_verify_user(org_id, user_id, project_name="default"):
} }
async def rl_check_validate_and_map(local_group_id, project_name="default"):
"""Internal: Check validation result and update rl_org_group mapping."""
keys = await _get_vendor_keys()
if not keys.get("success"):
return keys
dbname = _get_dbname()
db = DBPools()
async with db.sqlorContext(dbname) as sor:
recs = await sor.R("rl_asset_group", {"id": local_group_id})
if not recs:
return {"success": False, "message": "本地记录不存在"}
rec = recs[0]
byted_token = rec.byted_token
org_id = rec.org_id
# Call Vendor
client = _get_client("volcengine", keys["ak"], keys["sk"])
result = client.get_visual_validate_result(byted_token, project_name)
if "error" in result:
return {"success": False, "message": result.get("error", "查询失败")}
vendor_group_id = result.get("GroupId", "")
if not vendor_group_id:
return {"success": False, "message": "尚未完成认证或认证失败"}
# Update mapping table rl_org_group
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
mapping_id = getID()
async with db.sqlorContext(dbname) as sor:
# Check if mapping exists
existing = await sor.R("rl_org_group", {"org_id": org_id, "vendor": "volcengine"})
if existing:
await sor.U("rl_org_group", {
"vendor_group_id": vendor_group_id,
"local_group_id": local_group_id,
"update_time": now # add update_time field if needed, or just update
}, {"id": existing[0].id})
else:
await sor.I("rl_org_group", {
"id": mapping_id,
"org_id": org_id,
"vendor": "volcengine",
"vendor_group_id": vendor_group_id,
"local_group_id": local_group_id,
"status": "active",
"create_time": now
})
# Update rl_asset_group status
async with db.sqlorContext(dbname) as sor:
await sor.U("rl_asset_group", {
"vendor_group_id": vendor_group_id,
"status": "active",
"update_time": now,
}, {"id": local_group_id})
return {"success": True, "vendor_group_id": vendor_group_id}
async def rl_upload_user(org_id, vendor_group_id, source_url, asset_type, name, user_id): async def rl_upload_user(org_id, vendor_group_id, source_url, asset_type, name, user_id):
"""User proxy: Validate Org-Group mapping -> Get Keys -> Upload.""" """User proxy: Validate Org-Group mapping -> Upload via uapi."""
# 1. Validate: vendor_group_id belongs to this org
dbname = _get_dbname() dbname = _get_dbname()
db = DBPools() db = DBPools()
# 1. Validate: vendor_group_id belongs to this org
async with db.sqlorContext(dbname) as sor: async with db.sqlorContext(dbname) as sor:
recs = await sor.R("rl_org_group", { recs = await sor.R("rl_org_group", {
"org_id": org_id, "org_id": org_id,
@ -532,37 +515,32 @@ async def rl_upload_user(org_id, vendor_group_id, source_url, asset_type, name,
}) })
if not recs: if not recs:
return {"success": False, "message": "无效的素材组合ID或无权访问"} return {"success": False, "message": "无效的素材组合ID或无权访问"}
# Get local_group_id for asset record
local_group_id = recs[0].local_group_id local_group_id = recs[0].local_group_id
vendor = recs[0].vendor
# 2. Get Keys # 2. Upload via uapi
keys = await _get_vendor_keys() params = {
if not keys.get("success"): "GroupId": vendor_group_id,
return keys "URL": source_url,
"AssetType": asset_type,
"ProjectName": "default",
}
if name:
params["Name"] = name
result = await _call_vendor(vendor, "upload_asset", params)
# 3. Upload vendor_asset_id = result.get("Id", result.get("Result", {}).get("Id", ""))
client = _get_client("volcengine", keys["ak"], keys["sk"])
result = client.create_asset(
vendor_group_id, source_url, asset_type, name
)
vendor_asset_id = result.get("Id", "")
if not vendor_asset_id and "error" not in result:
r = result.get("Result", {})
vendor_asset_id = r.get("Id", "")
# 4. Save Asset Record
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S") now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
asset_id = getID() asset_id = getID()
asset_uri = f"asset://{vendor_asset_id}" if vendor_asset_id else "" asset_uri = f"asset://{vendor_asset_id}" if vendor_asset_id else ""
async with db.sqlorContext(dbname) as sor: async with db.sqlorContext(dbname) as sor:
await sor.I("rl_asset", { await sor.I("rl_asset", {
"id": asset_id, "id": asset_id,
"org_id": org_id, "org_id": org_id,
"group_id": local_group_id, "group_id": local_group_id,
"vendor": "volcengine", "vendor": vendor,
"vendor_asset_id": vendor_asset_id, "vendor_asset_id": vendor_asset_id,
"asset_type": asset_type, "asset_type": asset_type,
"name": name or source_url.split("/")[-1][:50], "name": name or source_url.split("/")[-1][:50],
@ -576,50 +554,48 @@ async def rl_upload_user(org_id, vendor_group_id, source_url, asset_type, name,
"update_time": now, "update_time": now,
}) })
has_error = "error" in result or "Error" in result
return { return {
"success": "error" not in result, "success": not has_error,
"id": asset_id, "id": asset_id,
"vendor_asset_id": vendor_asset_id, "vendor_asset_id": vendor_asset_id,
"status": "Processing", "status": "Processing",
"message": result.get("error", ""), "message": result.get("error", result.get("Message", "")),
} }
async def rl_sync_asset_status_user(org_id, asset_id, user_id): async def rl_sync_asset_status_user(org_id, asset_id, user_id):
"""User proxy: Validate ownership -> Get Keys -> Sync Status.""" """User proxy: Validate ownership -> Sync Status via uapi."""
dbname = _get_dbname() dbname = _get_dbname()
db = DBPools() db = DBPools()
# Validate asset belongs to org
async with db.sqlorContext(dbname) as sor: async with db.sqlorContext(dbname) as sor:
recs = await sor.R("rl_asset", {"id": asset_id, "org_id": org_id}) recs = await sor.R("rl_asset", {"id": asset_id, "org_id": org_id})
if not recs: if not recs:
return {"success": False, "message": "素材不存在或无权访问"} return {"success": False, "message": "素材不存在或无权访问"}
rec = recs[0] rec = recs[0]
vendor = rec.vendor
vendor_asset_id = rec.vendor_asset_id vendor_asset_id = rec.vendor_asset_id
project_name = rec.project_name or "default"
keys = await _get_vendor_keys() params = {
if not keys.get("success"): "Id": vendor_asset_id,
return keys "ProjectName": project_name,
}
result = await _call_vendor(vendor, "get_asset", params)
# Sync with vendor if "error" in result or "Error" in result:
client = _get_client("volcengine", keys["ak"], keys["sk"]) return {"success": False, "message": result.get("error", result.get("Message", "查询失败"))}
result = client.get_asset(vendor_asset_id)
if "error" in result: r = result.get("Result", result)
return {"success": False, "message": result.get("error", "查询失败")} status = r.get("Status", result.get("Status", ""))
url = r.get("URL", result.get("URL", ""))
status = result.get("Status", "")
if not status:
r = result.get("Result", {})
status = r.get("Status", result.get("status", ""))
url = result.get("URL", result.get("Result", {}).get("URL", ""))
# Update local
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S") now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
async with db.sqlorContext(dbname) as sor: async with db.sqlorContext(dbname) as sor:
upd = {"status": status, "update_time": now, "vendor_response": json.dumps(result, ensure_ascii=False)} upd = {"status": status, "update_time": now, "vendor_response": json.dumps(result, ensure_ascii=False)}
if url: upd["url"] = url if url:
upd["url"] = url
await sor.U("rl_asset", upd, {"id": asset_id}) await sor.U("rl_asset", upd, {"id": asset_id})
return {"success": True, "status": status, "url": url} return {"success": True, "status": status, "url": url}
@ -627,14 +603,14 @@ async def rl_sync_asset_status_user(org_id, asset_id, user_id):
async def rl_handle_callback(byted_token, project_name="default"): async def rl_handle_callback(byted_token, project_name="default"):
""" """
Callback handler: Volcengine POSTs here after H5 auth completes. Callback handler: vendor POSTs here after H5 auth completes.
Looks up local group by byted_token, queries vendor for result, Looks up local group by byted_token (which includes vendor field),
then registers rl_org_group mapping. queries vendor for result, then registers rl_org_group mapping.
""" """
dbname = _get_dbname() dbname = _get_dbname()
db = DBPools() db = DBPools()
# 1. Find local group by byted_token # 1. Find local group by byted_token — vendor comes from this record
async with db.sqlorContext(dbname) as sor: async with db.sqlorContext(dbname) as sor:
recs = await sor.R("rl_asset_group", {"byted_token": byted_token}) recs = await sor.R("rl_asset_group", {"byted_token": byted_token})
if not recs: if not recs:
@ -642,8 +618,8 @@ async def rl_handle_callback(byted_token, project_name="default"):
rec = recs[0] rec = recs[0]
local_group_id = rec.id local_group_id = rec.id
org_id = rec.org_id org_id = rec.org_id
vendor = rec.vendor
# Already processed?
if rec.status == "active" and rec.vendor_group_id: if rec.status == "active" and rec.vendor_group_id:
debug(f"callback already processed for group {local_group_id}") debug(f"callback already processed for group {local_group_id}")
return { return {
@ -653,37 +629,29 @@ async def rl_handle_callback(byted_token, project_name="default"):
"message": "已处理", "message": "已处理",
} }
# 2. Get vendor keys # 2. Query vendor for result via uapi
async with db.sqlorContext(dbname) as sor: params = {
vrecs = await sor.R("rl_vendor_config", {"vendor": "volcengine"}) "BytedToken": byted_token,
if not vrecs: "ProjectName": project_name,
return {"success": False, "message": "供应商配置不存在"} }
vrec = vrecs[0] result = await _call_vendor(vendor, "check_session", params)
if vrec.status != "active":
return {"success": False, "message": "供应商服务已停用"}
env = ServerEnv()
ak = env.password_decode(vrec.ak)
sk = env.password_decode(vrec.sk)
# 3. Query vendor for result if "error" in result or "Error" in result:
client = _get_client("volcengine", ak, sk)
result = client.get_visual_validate_result(byted_token, project_name)
if "error" in result:
error(f"callback query vendor error: {result}") error(f"callback query vendor error: {result}")
return {"success": False, "message": result.get("error", "查询失败")} return {"success": False, "message": result.get("error", result.get("Message", "查询失败"))}
vendor_group_id = result.get("GroupId", "") r = result.get("Result", result)
vendor_group_id = r.get("GroupId", result.get("GroupId", ""))
if not vendor_group_id: if not vendor_group_id:
return {"success": False, "message": "尚未完成认证或认证失败"} return {"success": False, "message": "尚未完成认证或认证失败"}
# 4. Register rl_org_group mapping # 3. Register rl_org_group mapping
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S") now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
mapping_id = getID() mapping_id = getID()
async with db.sqlorContext(dbname) as sor: async with db.sqlorContext(dbname) as sor:
existing = await sor.R("rl_org_group", { existing = await sor.R("rl_org_group", {
"org_id": org_id, "org_id": org_id,
"vendor": "volcengine", "vendor": vendor,
}) })
if existing: if existing:
await sor.U("rl_org_group", { await sor.U("rl_org_group", {
@ -695,14 +663,14 @@ async def rl_handle_callback(byted_token, project_name="default"):
await sor.I("rl_org_group", { await sor.I("rl_org_group", {
"id": mapping_id, "id": mapping_id,
"org_id": org_id, "org_id": org_id,
"vendor": "volcengine", "vendor": vendor,
"vendor_group_id": vendor_group_id, "vendor_group_id": vendor_group_id,
"local_group_id": local_group_id, "local_group_id": local_group_id,
"status": "active", "status": "active",
"create_time": now, "create_time": now,
}) })
# 5. Update rl_asset_group status # 4. Update rl_asset_group status
async with db.sqlorContext(dbname) as sor: async with db.sqlorContext(dbname) as sor:
await sor.U("rl_asset_group", { await sor.U("rl_asset_group", {
"vendor_group_id": vendor_group_id, "vendor_group_id": vendor_group_id,
@ -757,13 +725,12 @@ def load_reallife_asset():
g.rl_delete_group = rl_delete_group g.rl_delete_group = rl_delete_group
g.rl_sync_group_from_vendor = rl_sync_group_from_vendor g.rl_sync_group_from_vendor = rl_sync_group_from_vendor
g.rl_sync_assets_from_vendor = rl_sync_assets_from_vendor g.rl_sync_assets_from_vendor = rl_sync_assets_from_vendor
# Downapp user APIs # Downapp user APIs
g.rl_verify_user = rl_verify_user g.rl_verify_user = rl_verify_user
g.rl_check_validate_and_map = rl_check_validate_and_map
g.rl_upload_user = rl_upload_user g.rl_upload_user = rl_upload_user
g.rl_sync_asset_status_user = rl_sync_asset_status_user g.rl_sync_asset_status_user = rl_sync_asset_status_user
g.rl_handle_callback = rl_handle_callback g.rl_handle_callback = rl_handle_callback
g.rl_query_groups = rl_query_groups g.rl_query_groups = rl_query_groups
return True return True

View File

@ -0,0 +1,10 @@
import json
from appPublic.log import debug
rows = [
{"value": "Image", "text": "图片"},
{"value": "Video", "text": "视频"},
{"value": "Audio", "text": "音频"}
]
debug(f"get_asset_type_list: {rows}")
return json.dumps(rows, ensure_ascii=False)

View File

@ -0,0 +1,11 @@
org_id = (await get_userorgid()) or '0'
dbname = get_module_dbname('reallife_asset')
db = DBPools()
async with db.sqlorContext(dbname) as sor:
ns = dict(params_kw)
ns['org_id'] = org_id
recs = await sor.R('rl_org_group', ns)
total = len(recs)
data = [dict(r) for r in recs]
return {"data": data, "total": total, "status": "ok"}

View File

@ -0,0 +1,11 @@
org_id = (await get_userorgid()) or '0'
dbname = get_module_dbname('reallife_asset')
db = DBPools()
async with db.sqlorContext(dbname) as sor:
ns = dict(params_kw)
ns['org_id'] = org_id
recs = await sor.R('rl_vendor_config', ns)
total = len(recs)
data = [dict(r) for r in recs]
return {"data": data, "total": total, "status": "ok"}

View File

@ -0,0 +1,9 @@
import json
from appPublic.log import debug
rows = [
{"value": "active", "text": "启用"},
{"value": "inactive", "text": "停用"}
]
debug(f"get_status_list: {rows}")
return json.dumps(rows, ensure_ascii=False)

View File

@ -0,0 +1,16 @@
import json
from sqlor.dbpools import DBPools
from appPublic.log import debug
ns = params_kw.copy()
dbname = get_module_dbname('uapi')
db = DBPools()
async with db.sqlorContext(dbname) as sor:
recs = await sor.R("upapp", {"status": "1"})
rows = []
for r in recs:
rows.append({"value": r.id, "text": f"{r.name}({r.title})" if r.title else r.name})
debug(f"get_upapp_list: {rows}")
return json.dumps(rows, ensure_ascii=False)

View File

@ -0,0 +1,14 @@
import json
from sqlor.dbpools import DBPools
from appPublic.log import debug
ns = params_kw.copy()
dbname = get_module_dbname('reallife_asset')
db = DBPools()
async with db.sqlorContext(dbname) as sor:
recs = await sor.R("rl_vendor_config", {"status": "active"})
rows = []
for r in recs:
rows.append({"value": r.vendor, "text": r.vendor})
debug(f"get_vendor_list: {rows}")
return json.dumps(rows, ensure_ascii=False)

View File

@ -1,22 +1,26 @@
org_id = (await get_userorgid()) or '0' org_id = (await get_userorgid()) or '0'
user_id = await get_user() user_id = await get_user()
group_id = params_kw.get('group_id', '') vendor_group_id = params_kw.get('vendor_group_id', '')
source_url = params_kw.get('source_url', '') source_url = params_kw.get('source_url', '')
asset_type = params_kw.get('asset_type', 'Image') asset_type = params_kw.get('asset_type', 'Image')
name = params_kw.get('name', '') name = params_kw.get('name', '')
apikey = params_kw.get('apikey', '')
secretkey = params_kw.get('secretkey', '')
if not group_id: if not vendor_group_id:
return {"success": False, "message": "请选择素材组合"} return {"success": False, "message": "请提供供应商组合ID"}
if not source_url: if not source_url:
return {"success": False, "message": "请提供素材URL"} return {"success": False, "message": "请提供素材URL"}
if not apikey or not secretkey:
return {"success": False, "message": "请提供供应商 API Key"}
result = await rl_create_asset( result = await rl_create_asset(
org_id, group_id, source_url, org_id, vendor_group_id, source_url,
asset_type=asset_type, name=name, asset_type=asset_type, name=name,
apikey=apikey, secretkey=secretkey, user_id=user_id user_id=user_id
) )
return result
import json
return json.dumps({
"widgettype": "Message",
"options": {
"message": f"素材上传已提交: {result.get('vendor_asset_id', '')}" if result.get('success') else result.get('message', '上传失败'),
"type": "success" if result.get('success') else "error"
}
})

View File

@ -1,16 +1,16 @@
org_id = (await get_userorgid()) or '0' org_id = (await get_userorgid()) or '0'
rid = params_kw.get('id', '') rid = params_kw.get('id', '')
apikey = params_kw.get('apikey', '')
secretkey = params_kw.get('secretkey', '')
if apikey and secretkey: if not rid:
result = await rl_delete_asset(rid, apikey=apikey, secretkey=secretkey) return {"success": False, "message": "id required"}
return result
# Local-only delete result = await rl_delete_asset(rid)
dbname = get_module_dbname('reallife_asset')
db = DBPools()
async with db.sqlorContext(dbname) as sor:
await sor.D("rl_asset", {"id": rid, "org_id": org_id})
return {"success": True, "message": "本地删除成功"} import json
return json.dumps({
"widgettype": "Message",
"options": {
"message": "删除成功" if result.get("success") else result.get("message", "删除失败"),
"type": "success" if result.get("success") else "error"
}
})

View File

@ -1,18 +1,24 @@
org_id = (await get_userorgid()) or '0' org_id = (await get_userorgid()) or '0'
user_id = await get_user() user_id = await get_user()
vendor = params_kw.get('vendor', 'volcengine') vendor = params_kw.get('vendor', '')
callback_url = params_kw.get('callback_url', '') callback_url = params_kw.get('callback_url', '')
project_name = params_kw.get('project_name', 'default') project_name = params_kw.get('project_name', 'default')
apikey = params_kw.get('apikey', '')
secretkey = params_kw.get('secretkey', '')
if not vendor:
return {"success": False, "message": "请选择供应商"}
if not callback_url: if not callback_url:
return {"success": False, "message": "callback_url 不能为空"} return {"success": False, "message": "callback_url 不能为空"}
if not apikey or not secretkey:
return {"success": False, "message": "请提供供应商 API Key (apikey/secretkey)"}
result = await rl_create_validate_session( result = await rl_create_validate_session(
org_id, vendor, callback_url, project_name, org_id, vendor, callback_url, project_name,
apikey=apikey, secretkey=secretkey, user_id=user_id user_id=user_id
) )
return result
import json
return json.dumps({
"widgettype": "Message",
"options": {
"message": f"认证链接已生成: {result.get('h5_link', '')}",
"type": "success" if result.get('success') else "error"
}
})

View File

@ -1,17 +1,16 @@
org_id = (await get_userorgid()) or '0' org_id = (await get_userorgid()) or '0'
rid = params_kw.get('id', '') rid = params_kw.get('id', '')
apikey = params_kw.get('apikey', '')
secretkey = params_kw.get('secretkey', '')
if apikey and secretkey: if not rid:
result = await rl_delete_group(rid, apikey=apikey, secretkey=secretkey) return {"success": False, "message": "id required"}
return result
# Local-only delete result = await rl_delete_group(rid)
dbname = get_module_dbname('reallife_asset')
db = DBPools()
async with db.sqlorContext(dbname) as sor:
await sor.D("rl_asset", {"group_id": rid})
await sor.D("rl_asset_group", {"id": rid, "org_id": org_id})
return {"success": True, "message": "本地删除成功"} import json
return json.dumps({
"widgettype": "Message",
"options": {
"message": "删除成功" if result.get("success") else result.get("message", "删除失败"),
"type": "success" if result.get("success") else "error"
}
})

View File

@ -1,18 +1,15 @@
import json import json
# Volcengine callback POSTs JSON body with BytedToken and result info. # Vendor callback POSTs JSON body with BytedToken.
# The callback URL is configured when calling CreateVisualValidateSession. # The callback URL is configured when calling CreateVisualValidateSession.
# Typical payload: {"BytedToken": "...", "ReqUUID": "...", "Status": "..."} # Typical payload: {"BytedToken": "...", "ReqUUID": "...", "Status": "..."}
# Try to parse JSON body first
body_str = http_request.get("body", "") or "" body_str = http_request.get("body", "") or ""
byted_token = "" byted_token = ""
project_name = "default"
try: try:
body = json.loads(body_str) if body_str else {} body = json.loads(body_str) if body_str else {}
byted_token = body.get("BytedToken", "") byted_token = body.get("BytedToken", "")
# Also check alternative field names
if not byted_token: if not byted_token:
byted_token = body.get("byted_token", "") byted_token = body.get("byted_token", "")
if not byted_token: if not byted_token:
@ -20,12 +17,12 @@ try:
except: except:
byted_token = "" byted_token = ""
# Fallback: check query params
if not byted_token: if not byted_token:
byted_token = params_kw.get("BytedToken", params_kw.get("byted_token", "")) byted_token = params_kw.get("BytedToken", params_kw.get("byted_token", ""))
if not byted_token: if not byted_token:
return {"success": False, "message": "缺少 BytedToken 参数"} return {"success": False, "message": "缺少 BytedToken 参数"}
result = await rl_handle_callback(byted_token, project_name) # vendor is determined by looking up the session record
result = await rl_handle_callback(byted_token, project_name="default")
return result return result

View File

@ -2,19 +2,30 @@ org_id = params_kw.get('org_id', (await get_userorgid()) or '0')
id = params_kw.get('id', getID()) id = params_kw.get('id', getID())
now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
# Encrypt keys vendor = params_kw.get('vendor', '')
ak = params_kw.get('ak', '') upappid = params_kw.get('upappid', '')
sk = params_kw.get('sk', '') api_mapping = params_kw.get('api_mapping', '{}')
env = ServerEnv()
if ak: ak = env.password_encode(ak) if not vendor:
if sk: sk = env.password_encode(sk) return {"success": False, "message": "供应商标识不能为空"}
if not upappid:
return {"success": False, "message": "上位系统ID不能为空"}
# Validate api_mapping is valid JSON
import json
try:
api_mapping_dict = json.loads(api_mapping) if api_mapping else {}
api_mapping = json.dumps(api_mapping_dict, ensure_ascii=False)
except json.JSONDecodeError:
return {"success": False, "message": "API映射必须是有效的JSON"}
data = { data = {
"id": id, "id": id,
"org_id": org_id, "org_id": org_id,
"vendor": params_kw.get('vendor', 'volcengine'), "vendor": vendor,
"ak": ak, "vendor_title": params_kw.get('vendor_title', ''),
"sk": sk, "upappid": upappid,
"api_mapping": api_mapping,
"status": params_kw.get('status', 'active'), "status": params_kw.get('status', 'active'),
"callback_url": params_kw.get('callback_url', ''), "callback_url": params_kw.get('callback_url', ''),
"create_time": now, "create_time": now,

View File

@ -8,19 +8,19 @@ async with db.sqlorContext(dbname) as sor:
if not recs: return {"success": False, "message": "Not found"} if not recs: return {"success": False, "message": "Not found"}
upd = {} upd = {}
for k in ['status', 'callback_url', 'vendor']: for k in ['status', 'callback_url', 'vendor', 'vendor_title', 'upappid']:
if params_kw.get(k): if params_kw.get(k) is not None:
upd[k] = params_kw.get(k) upd[k] = params_kw.get(k)
# Encrypt keys # Update api_mapping if provided
ak = params_kw.get('ak', None) api_mapping = params_kw.get('api_mapping', None)
sk = params_kw.get('sk', None) if api_mapping is not None:
if ak is not None: import json
env = ServerEnv() try:
upd['ak'] = env.password_encode(ak) api_mapping_dict = json.loads(api_mapping) if api_mapping else {}
if sk is not None: upd['api_mapping'] = json.dumps(api_mapping_dict, ensure_ascii=False)
env = ServerEnv() except json.JSONDecodeError:
upd['sk'] = env.password_encode(sk) return {"success": False, "message": "API映射必须是有效的JSON"}
upd['update_time'] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") upd['update_time'] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")

View File

@ -1,4 +1,13 @@
vendor = params_kw.get('vendor', '')
project_name = params_kw.get('project_name', 'default') project_name = params_kw.get('project_name', 'default')
result = await rl_verify_user((await get_userorgid()) or '0', (await get_user()) or '', project_name) if not vendor:
return {"success": False, "message": "请指定供应商"}
result = await rl_verify_user(
(await get_userorgid()) or '0',
(await get_user()) or '',
vendor,
project_name
)
return result return result

View File

@ -1,11 +1,15 @@
asset_id = params_kw.get('asset_id', '') asset_id = params_kw.get('asset_id', '')
apikey = params_kw.get('apikey', '')
secretkey = params_kw.get('secretkey', '')
if not asset_id: if not asset_id:
return {"success": False, "message": "asset_id 不能为空"} return {"success": False, "message": "asset_id 不能为空"}
if not apikey or not secretkey:
return {"success": False, "message": "请提供供应商 API Key"}
result = await rl_sync_asset_status(asset_id, apikey=apikey, secretkey=secretkey) result = await rl_sync_asset_status(asset_id)
return result
import json
return json.dumps({
"widgettype": "Message",
"options": {
"message": f"状态同步完成: {result.get('status', '')}" if result.get('success') else result.get('message', '同步失败'),
"type": "success" if result.get('success') else "error"
}
})

View File

@ -1,14 +1,16 @@
org_id = (await get_userorgid()) or '0' org_id = (await get_userorgid()) or '0'
group_id = params_kw.get('group_id', '') group_id = params_kw.get('group_id', '')
apikey = params_kw.get('apikey', '')
secretkey = params_kw.get('secretkey', '')
if not group_id: if not group_id:
return {"success": False, "message": "group_id 不能为空"} return {"success": False, "message": "请选择素材组合"}
if not apikey or not secretkey:
return {"success": False, "message": "请提供供应商 API Key"}
result = await rl_sync_assets_from_vendor( result = await rl_sync_assets_from_vendor(org_id, group_id)
org_id, group_id, apikey=apikey, secretkey=secretkey
) import json
return result return json.dumps({
"widgettype": "Message",
"options": {
"message": f"素材同步完成,共 {result.get('synced', 0)} 条记录",
"type": "success" if result.get('success') else "error"
}
})

View File

@ -1,14 +1,17 @@
org_id = (await get_userorgid()) or '0' org_id = (await get_userorgid()) or '0'
vendor = params_kw.get('vendor', 'volcengine') vendor = params_kw.get('vendor', '')
apikey = params_kw.get('apikey', '')
secretkey = params_kw.get('secretkey', '')
project_name = params_kw.get('project_name', 'default') project_name = params_kw.get('project_name', 'default')
if not apikey or not secretkey: if not vendor:
return {"success": False, "message": "请提供供应商 API Key"} return {"success": False, "message": "请选择供应商"}
result = await rl_sync_group_from_vendor( result = await rl_sync_group_from_vendor(org_id, vendor, project_name)
org_id, vendor, apikey=apikey, secretkey=secretkey,
project_name=project_name import json
) return json.dumps({
return result "widgettype": "Message",
"options": {
"message": f"同步完成,共 {result.get('synced', 0)} 条记录",
"type": "success" if result.get('success') else "error"
}
})

View File

@ -25,9 +25,7 @@
{ {
"widgettype": "Button", "widgettype": "Button",
"options": { "options": {
"label": "上传素材", "label": "上传素材"
"bgcolor": "#1890ff",
"color": "#fff"
}, },
"binds": [ "binds": [
{ {
@ -52,4 +50,4 @@
} }
} }
] ]
} }

View File

@ -6,61 +6,130 @@
}, },
"subwidgets": [ "subwidgets": [
{ {
"widgettype": "Text", "widgettype": "HBox",
"options": { "options": {
"text": "创建真人认证", "width": "100%",
"fontSize": "20px", "alignItems": "center",
"fontWeight": "bold",
"marginBottom": "16px" "marginBottom": "16px"
} },
"subwidgets": [
{
"widgettype": "Title4",
"options": {
"text": "创建真人认证",
"fontWeight": "600"
}
},
{
"widgettype": "Filler"
},
{
"widgettype": "Button",
"options": {
"label": "返回首页"
},
"binds": [
{
"wid": "self",
"event": "click",
"actiontype": "urlwidget",
"target": "app.rl_content",
"options": {
"url": "{{entire_url('index.ui')}}"
},
"mode": "replace"
}
]
}
]
}, },
{ {
"widgettype": "Form", "widgettype": "VScrollPanel",
"id": "validate_form",
"options": { "options": {
"url": "{{entire_url('api/rl_asset_group_create.dspy')}}", "height": "500px"
"method": "POST", },
"fields": [ "subwidgets": [
{ {
"name": "vendor", "widgettype": "VBox",
"label": "供应商", "options": {
"type": "select", "padding": "16px",
"options": [ "spacing": 12
{ },
"value": "volcengine", "subwidgets": [
"text": "火山方舟" {
}, "widgettype": "Form",
{ "id": "validate_form",
"value": "kling", "options": {
"text": "可灵" "submit_url": "{{entire_url('api/rl_asset_group_create.dspy')}}",
"fields": [
{
"uitype": "code",
"name": "vendor",
"label": "供应商",
"dataurl": "{{entire_url('api/get_vendor_list.dspy')}}",
"data_field": "value",
"text_field": "text",
"required": true
},
{
"uitype": "str",
"name": "callback_url",
"label": "回调URL",
"placeholder": "https://your-domain.com/reallife_asset/api/rl_callback.dspy",
"required": true
},
{
"uitype": "str",
"name": "project_name",
"label": "项目名",
"placeholder": "default",
"value": "default"
}
]
} }
] },
}, {
{ "widgettype": "HBox",
"name": "callback_url", "options": {
"label": "回调URL", "gap": "12px",
"type": "text", "marginTop": "16px"
"placeholder": "https://your-domain.com/callback" },
}, "subwidgets": [
{ {
"name": "project_name", "widgettype": "Button",
"label": "项目名", "options": {
"type": "text", "label": "提交"
"default": "default" },
}, "binds": [
{ {
"name": "apikey", "wid": "self",
"label": "Access Key", "event": "click",
"type": "text" "actiontype": "script",
}, "target": "self",
{ "script": "var f=bricks.getWidgetById('validate_form',bricks.app);if(f)f.submit()"
"name": "secretkey", }
"label": "Secret Key", ]
"type": "password" },
} {
], "widgettype": "Button",
"submit_label": "生成认证链接" "options": {
} "label": "重置"
},
"binds": [
{
"wid": "self",
"event": "click",
"actiontype": "script",
"target": "self",
"script": "var f=bricks.getWidgetById('validate_form',bricks.app);if(f)f.reset()"
}
]
}
]
}
]
}
]
} }
] ]
} }

View File

@ -25,9 +25,7 @@
{ {
"widgettype": "Button", "widgettype": "Button",
"options": { "options": {
"label": "创建真人认证", "label": "创建真人认证"
"bgcolor": "#1890ff",
"color": "#fff"
}, },
"binds": [ "binds": [
{ {
@ -45,9 +43,7 @@
{ {
"widgettype": "Button", "widgettype": "Button",
"options": { "options": {
"label": "从供应商同步", "label": "从供应商同步"
"bgcolor": "#52c41a",
"color": "#fff"
}, },
"binds": [ "binds": [
{ {
@ -72,4 +68,4 @@
} }
} }
] ]
} }

View File

@ -23,13 +23,93 @@
}, },
"subwidgets": [ "subwidgets": [
{ {
"widgettype": "VBox", "widgettype": "Button",
"options": { "options": {
"bgcolor": "#FFFFFF", "css": "card",
"padding": "20px", "padding": "20px",
"cursor": "pointer", "cursor": "pointer",
"borderRadius": "8px", "borderRadius": "8px",
"boxShadow": "0 2px 8px rgba(0,0,0,0.1)" "border": "none"
},
"binds": [
{
"wid": "self",
"event": "click",
"actiontype": "urlwidget",
"target": "app.rl_content",
"options": {
"url": "{{entire_url('create_validate.ui')}}"
},
"mode": "replace"
}
],
"subwidgets": [
{
"widgettype": "Text",
"options": {
"text": "🔐 真人认证",
"fontSize": "18px",
"fontWeight": "bold"
}
},
{
"widgettype": "Text",
"options": {
"text": "创建真人认证会话获取H5认证链接",
"fontSize": "14px",
"color": "#666"
}
}
]
},
{
"widgettype": "Button",
"options": {
"css": "card",
"padding": "20px",
"cursor": "pointer",
"borderRadius": "8px",
"border": "none"
},
"binds": [
{
"wid": "self",
"event": "click",
"actiontype": "urlwidget",
"target": "app.rl_content",
"options": {
"url": "{{entire_url('upload_asset.ui')}}"
},
"mode": "replace"
}
],
"subwidgets": [
{
"widgettype": "Text",
"options": {
"text": "📤 上传素材",
"fontSize": "18px",
"fontWeight": "bold"
}
},
{
"widgettype": "Text",
"options": {
"text": "上传素材到供应商支持URL/base64",
"fontSize": "14px",
"color": "#666"
}
}
]
},
{
"widgettype": "Button",
"options": {
"css": "card",
"padding": "20px",
"cursor": "pointer",
"borderRadius": "8px",
"border": "none"
}, },
"binds": [ "binds": [
{ {
@ -63,13 +143,13 @@
] ]
}, },
{ {
"widgettype": "VBox", "widgettype": "Button",
"options": { "options": {
"bgcolor": "#FFFFFF", "css": "card",
"padding": "20px", "padding": "20px",
"cursor": "pointer", "cursor": "pointer",
"borderRadius": "8px", "borderRadius": "8px",
"boxShadow": "0 2px 8px rgba(0,0,0,0.1)" "border": "none"
}, },
"binds": [ "binds": [
{ {
@ -101,6 +181,86 @@
} }
} }
] ]
},
{
"widgettype": "Button",
"options": {
"css": "card",
"padding": "20px",
"cursor": "pointer",
"borderRadius": "8px",
"border": "none"
},
"binds": [
{
"wid": "self",
"event": "click",
"actiontype": "urlwidget",
"target": "app.rl_content",
"options": {
"url": "{{entire_url('vendor_config_manage.ui')}}"
},
"mode": "replace"
}
],
"subwidgets": [
{
"widgettype": "Text",
"options": {
"text": "⚙️ 供应商配置",
"fontSize": "18px",
"fontWeight": "bold"
}
},
{
"widgettype": "Text",
"options": {
"text": "管理供应商AK/SK和回调URL",
"fontSize": "14px",
"color": "#666"
}
}
]
},
{
"widgettype": "Button",
"options": {
"css": "card",
"padding": "20px",
"cursor": "pointer",
"borderRadius": "8px",
"border": "none"
},
"binds": [
{
"wid": "self",
"event": "click",
"actiontype": "urlwidget",
"target": "app.rl_content",
"options": {
"url": "{{entire_url('org_group_manage.ui')}}"
},
"mode": "replace"
}
],
"subwidgets": [
{
"widgettype": "Text",
"options": {
"text": "🔗 机构映射管理",
"fontSize": "18px",
"fontWeight": "bold"
}
},
{
"widgettype": "Text",
"options": {
"text": "查看/管理机构和供应商组合的映射关系",
"fontSize": "14px",
"color": "#666"
}
}
]
} }
] ]
}, },

View File

@ -0,0 +1,25 @@
{
"widgettype": "VBox",
"options": {
"width": "100%",
"padding": "16px"
},
"subwidgets": [
{
"widgettype": "Text",
"options": {
"text": "机构映射管理",
"fontSize": "20px",
"fontWeight": "bold",
"marginBottom": "16px"
}
},
{
"widgettype": "DataViewer",
"options": {
"data_url": "{{entire_url('api/get_rl_org_group_list.dspy')}}",
"crud_url": "{{entire_url('rl_org_group_list')}}"
}
}
]
}

View File

@ -15,42 +15,53 @@
} }
}, },
{ {
"widgettype": "Form", "widgettype": "VScrollPanel",
"id": "sync_form",
"options": { "options": {
"url": "{{entire_url('api/sync_from_vendor.dspy')}}", "height": "300px"
"method": "POST", },
"fields": [ "subwidgets": [
{ {
"name": "vendor", "widgettype": "VBox",
"label": "供应商", "options": {
"type": "select", "padding": "8px"
"options": [
{
"value": "volcengine",
"text": "火山方舟"
}
]
}, },
{ "subwidgets": [
"name": "project_name", {
"label": "项目名", "widgettype": "Form",
"type": "text", "id": "sync_form",
"default": "default" "options": {
}, "submit_url": "{{entire_url('api/sync_from_vendor.dspy')}}",
{ "fields": [
"name": "apikey", {
"label": "Access Key", "uitype": "code",
"type": "text" "name": "vendor",
}, "label": "供应商",
{ "dataurl": "{{entire_url('api/get_vendor_list.dspy')}}",
"name": "secretkey", "data_field": "value",
"label": "Secret Key", "text_field": "text",
"type": "password" "required": true
} },
], {
"submit_label": "开始同步" "uitype": "str",
} "name": "project_name",
"label": "项目名",
"placeholder": "default"
}
]
},
"binds": [
{
"wid": "self",
"event": "submited",
"actiontype": "script",
"target": "self",
"script": "await bricks.show_resp_message_or_error(event.params)"
}
]
}
]
}
]
} }
] ]
} }

View File

@ -6,70 +6,136 @@
}, },
"subwidgets": [ "subwidgets": [
{ {
"widgettype": "Text", "widgettype": "HBox",
"options": { "options": {
"text": "上传素材", "width": "100%",
"fontSize": "20px", "alignItems": "center",
"fontWeight": "bold",
"marginBottom": "16px" "marginBottom": "16px"
} },
"subwidgets": [
{
"widgettype": "Title4",
"options": {
"text": "上传素材",
"fontWeight": "600"
}
},
{
"widgettype": "Filler"
},
{
"widgettype": "Button",
"options": {
"label": "返回首页"
},
"binds": [
{
"wid": "self",
"event": "click",
"actiontype": "urlwidget",
"target": "app.rl_content",
"options": {
"url": "{{entire_url('index.ui')}}"
},
"mode": "replace"
}
]
}
]
}, },
{ {
"widgettype": "Form", "widgettype": "VScrollPanel",
"id": "upload_form",
"options": { "options": {
"url": "{{entire_url('api/rl_asset_create.dspy')}}", "height": "600px"
"method": "POST", },
"fields": [ "subwidgets": [
{ {
"name": "group_id", "widgettype": "VBox",
"label": "素材组合", "options": {
"type": "text", "padding": "16px",
"placeholder": "选择本地组合ID" "spacing": 12
}, },
{ "subwidgets": [
"name": "source_url", {
"label": "素材URL", "widgettype": "Form",
"type": "text", "id": "upload_form",
"placeholder": "https://... 可公开访问的图片/视频URL" "options": {
}, "submit_url": "{{entire_url('api/rl_asset_create.dspy')}}",
{ "fields": [
"name": "asset_type", {
"label": "素材类型", "uitype": "str",
"type": "select", "name": "vendor_group_id",
"options": [ "label": "供应商组合ID",
{ "placeholder": "输入已通过认证的 vendor_group_id",
"value": "Image", "required": true
"text": "图片" },
}, {
{ "uitype": "text",
"value": "Video", "name": "source_url",
"text": "视频" "label": "素材URL或base64",
}, "rows": 4,
{ "placeholder": "https://... 可公开访问的图片URL\n或\ndata:image/png;base64,...",
"value": "Audio", "required": true
"text": "音频" },
{
"uitype": "code",
"name": "asset_type",
"label": "素材类型",
"dataurl": "{{entire_url('api/get_asset_type_list.dspy')}}",
"data_field": "value",
"text_field": "text"
},
{
"uitype": "str",
"name": "name",
"label": "素材名称",
"placeholder": "可选默认使用URL最后一部分"
}
]
} }
] },
}, {
{ "widgettype": "HBox",
"name": "name", "options": {
"label": "素材名称", "gap": "12px",
"type": "text" "marginTop": "16px"
}, },
{ "subwidgets": [
"name": "apikey", {
"label": "Access Key", "widgettype": "Button",
"type": "text" "options": {
}, "label": "提交"
{ },
"name": "secretkey", "binds": [
"label": "Secret Key", {
"type": "password" "wid": "self",
} "event": "click",
], "actiontype": "script",
"submit_label": "上传素材" "target": "self",
} "script": "var f=bricks.getWidgetById('upload_form',bricks.app);if(f)f.submit()"
}
]
},
{
"widgettype": "Button",
"options": {
"label": "重置"
},
"binds": [
{
"wid": "self",
"event": "click",
"actiontype": "script",
"target": "self",
"script": "var f=bricks.getWidgetById('upload_form',bricks.app);if(f)f.reset()"
}
]
}
]
}
]
}
]
} }
] ]
} }

View File

@ -0,0 +1,155 @@
{
"widgettype": "VBox",
"options": {
"width": "100%",
"padding": "16px"
},
"subwidgets": [
{
"widgettype": "HBox",
"options": {
"width": "100%",
"alignItems": "center",
"marginBottom": "16px"
},
"subwidgets": [
{
"widgettype": "Title4",
"options": {
"text": "供应商配置",
"fontWeight": "600"
}
},
{
"widgettype": "Filler"
},
{
"widgettype": "Button",
"options": {
"label": "返回列表"
},
"binds": [
{
"wid": "self",
"event": "click",
"actiontype": "urlwidget",
"target": "app.rl_content",
"options": {
"url": "{{entire_url('vendor_config_manage.ui')}}"
},
"mode": "replace"
}
]
}
]
},
{
"widgettype": "VScrollPanel",
"options": {
"height": "600px"
},
"subwidgets": [
{
"widgettype": "VBox",
"options": {
"padding": "16px",
"spacing": 12
},
"subwidgets": [
{
"widgettype": "Form",
"id": "vendor_config_form",
"options": {
"submit_url": "{{entire_url('api/rl_vendor_config_create.dspy')}}",
"fields": [
{
"uitype": "str",
"name": "vendor",
"label": "供应商标识",
"placeholder": "如 volcengine",
"required": true
},
{
"uitype": "str",
"name": "vendor_title",
"label": "供应商名称",
"placeholder": "如火山引擎"
},
{
"uitype": "code",
"name": "upappid",
"label": "上位系统(uapi)",
"dataurl": "{{entire_url('api/get_upapp_list.dspy')}}",
"data_field": "value",
"text_field": "text",
"required": true
},
{
"uitype": "text",
"name": "api_mapping",
"label": "API映射(JSON)",
"rows": 8,
"placeholder": '{\"create_session\":\"CreateVisualValidateSession\",\"get_result\":\"GetVisualValidateResult\",\"upload_asset\":\"CreateAsset\",\"get_asset\":\"GetAsset\",\"list_assets\":\"ListAssets\",\"list_groups\":\"ListAssetGroups\",\"delete_asset\":\"DeleteAsset\",\"delete_group\":\"DeleteAssetGroup\"}'
},
{
"uitype": "code",
"name": "status",
"label": "状态",
"dataurl": "{{entire_url('api/get_status_list.dspy')}}",
"data_field": "value",
"text_field": "text"
},
{
"uitype": "str",
"name": "callback_url",
"label": "回调URL",
"placeholder": "https://your-domain.com/reallife_asset/api/rl_callback.dspy"
}
]
}
},
{
"widgettype": "HBox",
"options": {
"gap": "12px",
"marginTop": "16px"
},
"subwidgets": [
{
"widgettype": "Button",
"options": {
"label": "提交"
},
"binds": [
{
"wid": "self",
"event": "click",
"actiontype": "script",
"target": "self",
"script": "var f=bricks.getWidgetById('vendor_config_form',bricks.app);if(f)f.submit()"
}
]
},
{
"widgettype": "Button",
"options": {
"label": "重置"
},
"binds": [
{
"wid": "self",
"event": "click",
"actiontype": "script",
"target": "self",
"script": "var f=bricks.getWidgetById('vendor_config_form',bricks.app);if(f)f.reset()"
}
]
}
]
}
]
}
]
}
]
}

View File

@ -0,0 +1,25 @@
{
"widgettype": "VBox",
"options": {
"width": "100%",
"padding": "16px"
},
"subwidgets": [
{
"widgettype": "Text",
"options": {
"text": "供应商配置管理",
"fontSize": "20px",
"fontWeight": "bold",
"marginBottom": "16px"
}
},
{
"widgettype": "DataViewer",
"options": {
"data_url": "{{entire_url('api/get_rl_vendor_config_list.dspy')}}",
"crud_url": "{{entire_url('rl_vendor_config_list')}}"
}
}
]
}