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"},
"local_group_id": {"title": "本地组合ID", "widgettype": "Text"},
"status": {"title": "状态", "widgettype": "Text"},
"create_time": {"title": "创建时间", "widgettype": "Text"}
"create_time": {"title": "创建时间", "widgettype": "Text"},
"update_time": {"title": "更新时间", "widgettype": "Text"}
},
"editable": {
"new_data_url": "{{entire_url('../api/rl_org_group_create.dspy')}}",

View File

@ -3,10 +3,14 @@
"alias": "rl_vendor_config_list",
"title": "供应商配置管理",
"params": {
"sortby": ["create_time desc"],
"logined_userorgid": "org_id",
"browserfields": {
"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"},
"callback_url": {"title": "回调URL", "widgettype": "Text"},
"create_time": {"title": "创建时间", "widgettype": "Text"},

View File

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

View File

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

View File

@ -1,7 +1,8 @@
"""
reallife_asset module - Real Person Portrait Asset Management.
Supports multiple vendors (Volcengine Ark, etc.) for managing
real person portrait asset groups and assets.
Supports multiple vendors via Sage uapi gateway.
Vendor API routing: rl_vendor_config.api_mapping JSON maps internal
operations to uapi apinames. Each vendor has its own upappid.
"""
import json
from datetime import datetime
@ -12,8 +13,7 @@ from ahserver.serverenv import ServerEnv
from appPublic.log import debug, exception, error
from appPublic.uniqueID import getID
from appPublic.dictObject import DictObject
from .volcengine_client import get_vendor_client
from uapi.uapi import UpAppApi
MODULE_NAME = "reallife_asset"
@ -23,28 +23,88 @@ def _get_dbname():
return f(MODULE_NAME)
def _get_client(vendor, apikey, secretkey):
"""Get vendor API client."""
return get_vendor_client(vendor, apikey, secretkey)
async def _get_vendor_config(vendor):
"""Look up vendor config: upappid + api_mapping from rl_vendor_config."""
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,
project_name="default",
apikey=None, secretkey=None,
user_id=None):
"""Create H5 verification session for real person auth."""
client = _get_client(vendor, apikey, secretkey)
result = client.create_visual_validate_session(callback_url, project_name)
params = {
"CallbackURL": callback_url or "",
"ProjectName": project_name,
}
result = await _call_vendor(vendor, "create_session", params)
if "error" in result:
return {"success": False, "message": result.get("error", "API调用失败")}
if "error" in result or "Error" in result:
return {"success": False, "message": result.get("error", result.get("Message", "API调用失败"))}
byted_token = result.get("BytedToken", "")
h5_link = result.get("H5Link", "")
byted_token = result.get("BytedToken", result.get("Result", {}).get("BytedToken", ""))
h5_link = result.get("H5Link", result.get("Result", {}).get("H5Link", ""))
# Save to local DB
dbname = _get_dbname()
@ -63,7 +123,7 @@ async def rl_create_validate_session(org_id, vendor, callback_url,
"status": "pending",
"byted_token": byted_token,
"h5_link": h5_link,
"callback_url": callback_url,
"callback_url": callback_url or "",
"created_by": user_id or "",
"create_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,
apikey=None, secretkey=None):
async def rl_check_validate_result(local_group_id, vendor, project_name="default"):
"""Check real person validation result and get vendor Group ID."""
dbname = _get_dbname()
db = DBPools()
@ -87,15 +146,18 @@ async def rl_check_validate_result(local_group_id, vendor,
return {"success": False, "message": "本地记录不存在"}
rec = recs[0]
byted_token = rec.byted_token
project_name = rec.project_name or "default"
client = _get_client(vendor, apikey, secretkey)
result = client.get_visual_validate_result(byted_token, project_name)
params = {
"BytedToken": byted_token,
"ProjectName": project_name,
}
result = await _call_vendor(vendor, "check_session", params)
if "error" in result:
return {"success": False, "message": result.get("error", "查询失败")}
if "error" in result or "Error" in result:
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:
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}
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="",
vendor=None, apikey=None, secretkey=None,
vendor=None, project_name="default",
user_id=None):
"""Upload asset to vendor and create local record."""
dbname = _get_dbname()
db = DBPools()
# Find the group to get vendor and local_group_id
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:
return {"success": False, "message": "素材组合不存在"}
grp = recs[0]
vendor = vendor or grp.vendor
vendor_group_id = grp.vendor_group_id
project_name = grp.project_name or "default"
local_group_id = grp.id
project_name = grp.project_name or project_name
if not vendor_group_id:
return {"success": False, "message": "素材组合尚未完成真人认证"}
# Call vendor API
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)
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", "")
vendor_asset_id = result.get("Id", result.get("Result", {}).get("Id", ""))
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
asset_id = getID()
@ -166,17 +229,17 @@ async def rl_create_asset(org_id, local_group_id, source_url,
"update_time": now,
})
has_error = "error" in result or "Error" in result
return {
"success": "error" not in result,
"success": not has_error,
"id": asset_id,
"vendor_asset_id": vendor_asset_id,
"status": "Processing",
"message": result.get("error", ""),
"message": result.get("error", result.get("Message", "")),
}
async def rl_sync_asset_status(asset_id, vendor=None,
apikey=None, secretkey=None):
async def rl_sync_asset_status(asset_id):
"""Sync asset status from vendor."""
dbname = _get_dbname()
db = DBPools()
@ -185,25 +248,25 @@ async def rl_sync_asset_status(asset_id, vendor=None,
if not recs:
return {"success": False, "message": "素材不存在"}
rec = recs[0]
vendor = vendor or rec.vendor
vendor = rec.vendor
vendor_asset_id = rec.vendor_asset_id
project_name = rec.project_name or "default"
if not vendor_asset_id:
return {"success": False, "message": "无供应商端资产ID"}
client = _get_client(vendor, apikey, secretkey)
result = client.get_asset(vendor_asset_id, project_name)
params = {
"Id": vendor_asset_id,
"ProjectName": project_name,
}
result = await _call_vendor(vendor, "get_asset", params)
if "error" in result:
return {"success": False, "message": result.get("error", "查询失败")}
if "error" in result or "Error" in result:
return {"success": False, "message": result.get("error", result.get("Message", "查询失败"))}
# Extract status from result (may be nested under Result)
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", ""))
r = result.get("Result", result)
status = r.get("Status", result.get("Status", ""))
url = r.get("URL", result.get("URL", ""))
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
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}
async def rl_delete_asset(asset_id, vendor=None,
apikey=None, secretkey=None):
async def rl_delete_asset(asset_id):
"""Delete asset from vendor and local DB."""
dbname = _get_dbname()
db = DBPools()
@ -229,15 +291,14 @@ async def rl_delete_asset(asset_id, vendor=None,
if not recs:
return {"success": False, "message": "素材不存在"}
rec = recs[0]
vendor = vendor or rec.vendor
vendor = rec.vendor
vendor_asset_id = rec.vendor_asset_id
project_name = rec.project_name or "default"
# Delete from vendor
if vendor_asset_id:
client = _get_client(vendor, apikey, secretkey)
result = client.delete_asset(vendor_asset_id, project_name)
debug(f"vendor delete asset: {result}")
params = {"Id": vendor_asset_id, "ProjectName": project_name}
await _call_vendor(vendor, "delete_asset", params)
# Delete local
async with db.sqlorContext(dbname) as sor:
@ -246,8 +307,7 @@ async def rl_delete_asset(asset_id, vendor=None,
return {"success": True}
async def rl_delete_group(local_group_id, vendor=None,
apikey=None, secretkey=None):
async def rl_delete_group(local_group_id):
"""Delete asset group from vendor and local DB (cascade)."""
dbname = _get_dbname()
db = DBPools()
@ -256,15 +316,14 @@ async def rl_delete_group(local_group_id, vendor=None,
if not recs:
return {"success": False, "message": "素材组合不存在"}
rec = recs[0]
vendor = vendor or rec.vendor
vendor = rec.vendor
vendor_group_id = rec.vendor_group_id
project_name = rec.project_name or "default"
# Delete from vendor
if vendor_group_id:
client = _get_client(vendor, apikey, secretkey)
result = client.delete_asset_group(vendor_group_id, project_name)
debug(f"vendor delete group: {result}")
params = {"Id": vendor_group_id, "ProjectName": project_name}
await _call_vendor(vendor, "delete_group", params)
# Delete local (cascade)
async with db.sqlorContext(dbname) as sor:
@ -274,12 +333,14 @@ async def rl_delete_group(local_group_id, vendor=None,
return {"success": True}
async def rl_sync_group_from_vendor(org_id, vendor,
apikey=None, secretkey=None,
project_name="default"):
async def rl_sync_group_from_vendor(org_id, vendor, project_name="default"):
"""Sync asset groups from vendor to local DB."""
client = _get_client(vendor, apikey, secretkey)
result = client.list_asset_groups(project_name=project_name)
params = {
"Filter": {"GroupType": "LivenessFace"},
"PageNumber": 1,
"PageSize": 100,
}
result = await _call_vendor(vendor, "list_groups", params)
items = result.get("Items", result.get("Result", {}).get("Items", []))
synced = 0
@ -291,7 +352,6 @@ async def rl_sync_group_from_vendor(org_id, vendor,
vgid = item.get("Id", "")
if not vgid:
continue
# Check if exists
existing = await sor.R("rl_asset_group", {
"vendor": vendor,
"vendor_group_id": vgid,
@ -325,8 +385,7 @@ async def rl_sync_group_from_vendor(org_id, vendor,
return {"success": True, "synced": synced}
async def rl_sync_assets_from_vendor(org_id, local_group_id,
vendor=None, apikey=None, secretkey=None):
async def rl_sync_assets_from_vendor(org_id, local_group_id):
"""Sync assets for a group from vendor to local DB."""
dbname = _get_dbname()
db = DBPools()
@ -335,15 +394,19 @@ async def rl_sync_assets_from_vendor(org_id, local_group_id,
if not recs:
return {"success": False, "message": "素材组合不存在"}
grp = recs[0]
vendor = vendor or grp.vendor
vendor = grp.vendor
vendor_group_id = grp.vendor_group_id
project_name = grp.project_name or "default"
if not vendor_group_id:
return {"success": False, "message": "无供应商端组合ID"}
client = _get_client(vendor, apikey, secretkey)
result = client.list_assets(group_ids=[vendor_group_id])
params = {
"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", []))
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"):
"""Helper: Get vendor AK/SK from config table."""
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": "供应商配置不存在"}
rec = recs[0]
if rec.status != "active":
return {"success": False, "message": f"供应商服务已停用"}
async def rl_verify_user(org_id, user_id, vendor, project_name="default"):
"""User proxy: Create H5 verification session via uapi gateway."""
callback_cfg = await _get_vendor_config(vendor)
if not callback_cfg.get("success"):
return callback_cfg
callback_url = callback_cfg.get("callback_url", "")
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)
if "error" in result or "Error" in result:
return {"success": False, "message": result.get("error", result.get("Message", "API调用失败"))}
async def rl_verify_user(org_id, user_id, project_name="default"):
"""User proxy: Verify vendor config -> Call vendor -> Save org-group mapping."""
keys = await _get_vendor_keys()
if not keys.get("success"):
return keys
byted_token = result.get("BytedToken", result.get("Result", {}).get("BytedToken", ""))
h5_link = result.get("H5Link", result.get("Result", {}).get("H5Link", ""))
# 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", "")
h5_link = result.get("H5Link", "")
# Save local record for checking status later
# We use rl_asset_group for temporary state tracking
gid = getID()
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
db = DBPools()
@ -436,7 +480,7 @@ async def rl_verify_user(org_id, user_id, project_name="default"):
await sor.I("rl_asset_group", {
"id": gid,
"org_id": org_id,
"vendor": "volcengine",
"vendor": vendor,
"name": f"待认证-{user_id}",
"title": f"待认证",
"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):
"""User proxy: Validate Org-Group mapping -> Get Keys -> Upload."""
# 1. Validate: vendor_group_id belongs to this org
"""User proxy: Validate Org-Group mapping -> Upload via uapi."""
dbname = _get_dbname()
db = DBPools()
# 1. Validate: vendor_group_id belongs to this org
async with db.sqlorContext(dbname) as sor:
recs = await sor.R("rl_org_group", {
"org_id": org_id,
@ -532,27 +515,22 @@ async def rl_upload_user(org_id, vendor_group_id, source_url, asset_type, name,
})
if not recs:
return {"success": False, "message": "无效的素材组合ID或无权访问"}
# Get local_group_id for asset record
local_group_id = recs[0].local_group_id
vendor = recs[0].vendor
# 2. Get Keys
keys = await _get_vendor_keys()
if not keys.get("success"):
return keys
# 2. Upload via uapi
params = {
"GroupId": vendor_group_id,
"URL": source_url,
"AssetType": asset_type,
"ProjectName": "default",
}
if name:
params["Name"] = name
result = await _call_vendor(vendor, "upload_asset", params)
# 3. Upload
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", result.get("Result", {}).get("Id", ""))
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")
asset_id = getID()
asset_uri = f"asset://{vendor_asset_id}" if vendor_asset_id else ""
@ -562,7 +540,7 @@ async def rl_upload_user(org_id, vendor_group_id, source_url, asset_type, name,
"id": asset_id,
"org_id": org_id,
"group_id": local_group_id,
"vendor": "volcengine",
"vendor": vendor,
"vendor_asset_id": vendor_asset_id,
"asset_type": asset_type,
"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,
})
has_error = "error" in result or "Error" in result
return {
"success": "error" not in result,
"success": not has_error,
"id": asset_id,
"vendor_asset_id": vendor_asset_id,
"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):
"""User proxy: Validate ownership -> Get Keys -> Sync Status."""
"""User proxy: Validate ownership -> Sync Status via uapi."""
dbname = _get_dbname()
db = DBPools()
# Validate asset belongs to org
async with db.sqlorContext(dbname) as sor:
recs = await sor.R("rl_asset", {"id": asset_id, "org_id": org_id})
if not recs:
return {"success": False, "message": "素材不存在或无权访问"}
rec = recs[0]
vendor = rec.vendor
vendor_asset_id = rec.vendor_asset_id
project_name = rec.project_name or "default"
keys = await _get_vendor_keys()
if not keys.get("success"):
return keys
params = {
"Id": vendor_asset_id,
"ProjectName": project_name,
}
result = await _call_vendor(vendor, "get_asset", params)
# Sync with vendor
client = _get_client("volcengine", keys["ak"], keys["sk"])
result = client.get_asset(vendor_asset_id)
if "error" in result or "Error" in result:
return {"success": False, "message": result.get("error", result.get("Message", "查询失败"))}
if "error" in result:
return {"success": False, "message": result.get("error", "查询失败")}
r = result.get("Result", result)
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")
async with db.sqlorContext(dbname) as sor:
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})
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"):
"""
Callback handler: Volcengine POSTs here after H5 auth completes.
Looks up local group by byted_token, queries vendor for result,
then registers rl_org_group mapping.
Callback handler: vendor POSTs here after H5 auth completes.
Looks up local group by byted_token (which includes vendor field),
queries vendor for result, then registers rl_org_group mapping.
"""
dbname = _get_dbname()
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:
recs = await sor.R("rl_asset_group", {"byted_token": byted_token})
if not recs:
@ -642,8 +618,8 @@ async def rl_handle_callback(byted_token, project_name="default"):
rec = recs[0]
local_group_id = rec.id
org_id = rec.org_id
vendor = rec.vendor
# Already processed?
if rec.status == "active" and rec.vendor_group_id:
debug(f"callback already processed for group {local_group_id}")
return {
@ -653,37 +629,29 @@ async def rl_handle_callback(byted_token, project_name="default"):
"message": "已处理",
}
# 2. Get vendor keys
async with db.sqlorContext(dbname) as sor:
vrecs = await sor.R("rl_vendor_config", {"vendor": "volcengine"})
if not vrecs:
return {"success": False, "message": "供应商配置不存在"}
vrec = vrecs[0]
if vrec.status != "active":
return {"success": False, "message": "供应商服务已停用"}
env = ServerEnv()
ak = env.password_decode(vrec.ak)
sk = env.password_decode(vrec.sk)
# 2. Query vendor for result via uapi
params = {
"BytedToken": byted_token,
"ProjectName": project_name,
}
result = await _call_vendor(vendor, "check_session", params)
# 3. Query vendor for result
client = _get_client("volcengine", ak, sk)
result = client.get_visual_validate_result(byted_token, project_name)
if "error" in result:
if "error" in result or "Error" in 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:
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")
mapping_id = getID()
async with db.sqlorContext(dbname) as sor:
existing = await sor.R("rl_org_group", {
"org_id": org_id,
"vendor": "volcengine",
"vendor": vendor,
})
if existing:
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", {
"id": mapping_id,
"org_id": org_id,
"vendor": "volcengine",
"vendor": vendor,
"vendor_group_id": vendor_group_id,
"local_group_id": local_group_id,
"status": "active",
"create_time": now,
})
# 5. Update rl_asset_group status
# 4. Update rl_asset_group status
async with db.sqlorContext(dbname) as sor:
await sor.U("rl_asset_group", {
"vendor_group_id": vendor_group_id,
@ -760,7 +728,6 @@ def load_reallife_asset():
# Downapp user APIs
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_sync_asset_status_user = rl_sync_asset_status_user
g.rl_handle_callback = rl_handle_callback

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'
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', '')
asset_type = params_kw.get('asset_type', 'Image')
name = params_kw.get('name', '')
apikey = params_kw.get('apikey', '')
secretkey = params_kw.get('secretkey', '')
if not group_id:
return {"success": False, "message": "请选择素材组合"}
if not vendor_group_id:
return {"success": False, "message": "请提供供应商组合ID"}
if not source_url:
return {"success": False, "message": "请提供素材URL"}
if not apikey or not secretkey:
return {"success": False, "message": "请提供供应商 API Key"}
result = await rl_create_asset(
org_id, group_id, source_url,
org_id, vendor_group_id, source_url,
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'
rid = params_kw.get('id', '')
apikey = params_kw.get('apikey', '')
secretkey = params_kw.get('secretkey', '')
if apikey and secretkey:
result = await rl_delete_asset(rid, apikey=apikey, secretkey=secretkey)
return result
if not rid:
return {"success": False, "message": "id required"}
# Local-only delete
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})
result = await rl_delete_asset(rid)
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'
user_id = await get_user()
vendor = params_kw.get('vendor', 'volcengine')
vendor = params_kw.get('vendor', '')
callback_url = params_kw.get('callback_url', '')
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:
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(
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'
rid = params_kw.get('id', '')
apikey = params_kw.get('apikey', '')
secretkey = params_kw.get('secretkey', '')
if apikey and secretkey:
result = await rl_delete_group(rid, apikey=apikey, secretkey=secretkey)
return result
if not rid:
return {"success": False, "message": "id required"}
# Local-only delete
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})
result = await rl_delete_group(rid)
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
# 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.
# Typical payload: {"BytedToken": "...", "ReqUUID": "...", "Status": "..."}
# Try to parse JSON body first
body_str = http_request.get("body", "") or ""
byted_token = ""
project_name = "default"
try:
body = json.loads(body_str) if body_str else {}
byted_token = body.get("BytedToken", "")
# Also check alternative field names
if not byted_token:
byted_token = body.get("byted_token", "")
if not byted_token:
@ -20,12 +17,12 @@ try:
except:
byted_token = ""
# Fallback: check query params
if not byted_token:
byted_token = params_kw.get("BytedToken", params_kw.get("byted_token", ""))
if not byted_token:
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

View File

@ -2,19 +2,30 @@ org_id = params_kw.get('org_id', (await get_userorgid()) or '0')
id = params_kw.get('id', getID())
now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
# Encrypt keys
ak = params_kw.get('ak', '')
sk = params_kw.get('sk', '')
env = ServerEnv()
if ak: ak = env.password_encode(ak)
if sk: sk = env.password_encode(sk)
vendor = params_kw.get('vendor', '')
upappid = params_kw.get('upappid', '')
api_mapping = params_kw.get('api_mapping', '{}')
if not vendor:
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 = {
"id": id,
"org_id": org_id,
"vendor": params_kw.get('vendor', 'volcengine'),
"ak": ak,
"sk": sk,
"vendor": vendor,
"vendor_title": params_kw.get('vendor_title', ''),
"upappid": upappid,
"api_mapping": api_mapping,
"status": params_kw.get('status', 'active'),
"callback_url": params_kw.get('callback_url', ''),
"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"}
upd = {}
for k in ['status', 'callback_url', 'vendor']:
if params_kw.get(k):
for k in ['status', 'callback_url', 'vendor', 'vendor_title', 'upappid']:
if params_kw.get(k) is not None:
upd[k] = params_kw.get(k)
# Encrypt keys
ak = params_kw.get('ak', None)
sk = params_kw.get('sk', None)
if ak is not None:
env = ServerEnv()
upd['ak'] = env.password_encode(ak)
if sk is not None:
env = ServerEnv()
upd['sk'] = env.password_encode(sk)
# Update api_mapping if provided
api_mapping = params_kw.get('api_mapping', None)
if api_mapping is not None:
import json
try:
api_mapping_dict = json.loads(api_mapping) if api_mapping else {}
upd['api_mapping'] = json.dumps(api_mapping_dict, ensure_ascii=False)
except json.JSONDecodeError:
return {"success": False, "message": "API映射必须是有效的JSON"}
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')
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

View File

@ -1,11 +1,15 @@
asset_id = params_kw.get('asset_id', '')
apikey = params_kw.get('apikey', '')
secretkey = params_kw.get('secretkey', '')
if not 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)
return result
result = await rl_sync_asset_status(asset_id)
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'
group_id = params_kw.get('group_id', '')
apikey = params_kw.get('apikey', '')
secretkey = params_kw.get('secretkey', '')
if not group_id:
return {"success": False, "message": "group_id 不能为空"}
if not apikey or not secretkey:
return {"success": False, "message": "请提供供应商 API Key"}
return {"success": False, "message": "请选择素材组合"}
result = await rl_sync_assets_from_vendor(
org_id, group_id, apikey=apikey, secretkey=secretkey
)
return result
result = await rl_sync_assets_from_vendor(org_id, group_id)
import json
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'
vendor = params_kw.get('vendor', 'volcengine')
apikey = params_kw.get('apikey', '')
secretkey = params_kw.get('secretkey', '')
vendor = params_kw.get('vendor', '')
project_name = params_kw.get('project_name', 'default')
if not apikey or not secretkey:
return {"success": False, "message": "请提供供应商 API Key"}
if not vendor:
return {"success": False, "message": "请选择供应商"}
result = await rl_sync_group_from_vendor(
org_id, vendor, apikey=apikey, secretkey=secretkey,
project_name=project_name
)
return result
result = await rl_sync_group_from_vendor(org_id, vendor, project_name)
import json
return json.dumps({
"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",
"options": {
"label": "上传素材",
"bgcolor": "#1890ff",
"color": "#fff"
"label": "上传素材"
},
"binds": [
{

View File

@ -6,61 +6,130 @@
},
"subwidgets": [
{
"widgettype": "Text",
"widgettype": "HBox",
"options": {
"text": "创建真人认证",
"fontSize": "20px",
"fontWeight": "bold",
"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('index.ui')}}"
},
"mode": "replace"
}
]
}
]
},
{
"widgettype": "Form",
"id": "validate_form",
"widgettype": "VScrollPanel",
"options": {
"url": "{{entire_url('api/rl_asset_group_create.dspy')}}",
"method": "POST",
"fields": [
{
"name": "vendor",
"label": "供应商",
"type": "select",
"options": [
{
"value": "volcengine",
"text": "火山方舟"
},
{
"value": "kling",
"text": "可灵"
"height": "500px"
},
"subwidgets": [
{
"widgettype": "VBox",
"options": {
"padding": "16px",
"spacing": 12
},
"subwidgets": [
{
"widgettype": "Form",
"id": "validate_form",
"options": {
"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"
}
]
}
]
},
{
"name": "callback_url",
"label": "回调URL",
"type": "text",
"placeholder": "https://your-domain.com/callback"
},
{
"name": "project_name",
"label": "项目名",
"type": "text",
"default": "default"
},
{
"name": "apikey",
"label": "Access Key",
"type": "text"
},
{
"name": "secretkey",
"label": "Secret Key",
"type": "password"
}
],
"submit_label": "生成认证链接"
}
},
{
"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('validate_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('validate_form',bricks.app);if(f)f.reset()"
}
]
}
]
}
]
}
]
}
]
}

View File

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

View File

@ -23,13 +23,93 @@
},
"subwidgets": [
{
"widgettype": "VBox",
"widgettype": "Button",
"options": {
"bgcolor": "#FFFFFF",
"css": "card",
"padding": "20px",
"cursor": "pointer",
"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": [
{
@ -63,13 +143,13 @@
]
},
{
"widgettype": "VBox",
"widgettype": "Button",
"options": {
"bgcolor": "#FFFFFF",
"css": "card",
"padding": "20px",
"cursor": "pointer",
"borderRadius": "8px",
"boxShadow": "0 2px 8px rgba(0,0,0,0.1)"
"border": "none"
},
"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",
"id": "sync_form",
"widgettype": "VScrollPanel",
"options": {
"url": "{{entire_url('api/sync_from_vendor.dspy')}}",
"method": "POST",
"fields": [
{
"name": "vendor",
"label": "供应商",
"type": "select",
"options": [
{
"value": "volcengine",
"text": "火山方舟"
}
]
"height": "300px"
},
"subwidgets": [
{
"widgettype": "VBox",
"options": {
"padding": "8px"
},
{
"name": "project_name",
"label": "项目名",
"type": "text",
"default": "default"
},
{
"name": "apikey",
"label": "Access Key",
"type": "text"
},
{
"name": "secretkey",
"label": "Secret Key",
"type": "password"
}
],
"submit_label": "开始同步"
}
"subwidgets": [
{
"widgettype": "Form",
"id": "sync_form",
"options": {
"submit_url": "{{entire_url('api/sync_from_vendor.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": "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": [
{
"widgettype": "Text",
"widgettype": "HBox",
"options": {
"text": "上传素材",
"fontSize": "20px",
"fontWeight": "bold",
"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('index.ui')}}"
},
"mode": "replace"
}
]
}
]
},
{
"widgettype": "Form",
"id": "upload_form",
"widgettype": "VScrollPanel",
"options": {
"url": "{{entire_url('api/rl_asset_create.dspy')}}",
"method": "POST",
"fields": [
{
"name": "group_id",
"label": "素材组合",
"type": "text",
"placeholder": "选择本地组合ID"
"height": "600px"
},
"subwidgets": [
{
"widgettype": "VBox",
"options": {
"padding": "16px",
"spacing": 12
},
{
"name": "source_url",
"label": "素材URL",
"type": "text",
"placeholder": "https://... 可公开访问的图片/视频URL"
},
{
"name": "asset_type",
"label": "素材类型",
"type": "select",
"options": [
{
"value": "Image",
"text": "图片"
},
{
"value": "Video",
"text": "视频"
},
{
"value": "Audio",
"text": "音频"
"subwidgets": [
{
"widgettype": "Form",
"id": "upload_form",
"options": {
"submit_url": "{{entire_url('api/rl_asset_create.dspy')}}",
"fields": [
{
"uitype": "str",
"name": "vendor_group_id",
"label": "供应商组合ID",
"placeholder": "输入已通过认证的 vendor_group_id",
"required": true
},
{
"uitype": "text",
"name": "source_url",
"label": "素材URL或base64",
"rows": 4,
"placeholder": "https://... 可公开访问的图片URL\n或\ndata:image/png;base64,...",
"required": true
},
{
"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最后一部分"
}
]
}
]
},
{
"name": "name",
"label": "素材名称",
"type": "text"
},
{
"name": "apikey",
"label": "Access Key",
"type": "text"
},
{
"name": "secretkey",
"label": "Secret Key",
"type": "password"
}
],
"submit_label": "上传素材"
}
},
{
"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('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')}}"
}
}
]
}