From a21eabbb116c150be257432d62e53fc3acf22475 Mon Sep 17 00:00:00 2001 From: yumoqing Date: Thu, 28 May 2026 18:49:46 +0800 Subject: [PATCH] =?UTF-8?q?refactor(reallife=5Fasset):=20uapi=E7=BD=91?= =?UTF-8?q?=E5=85=B3=E6=9E=B6=E6=9E=84=E9=87=8D=E6=9E=84=20+=20UI=E5=85=A8?= =?UTF-8?q?=E9=9D=A2=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 架构变更: - 废弃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透传 --- json/rl_org_group_list.json | 3 +- json/rl_vendor_config_list.json | 6 +- models/rl_org_group.json | 5 + models/rl_vendor_config.json | 33 +- reallife_asset/init.py | 465 ++++++++++----------- wwwroot/api/get_asset_type_list.dspy | 10 + wwwroot/api/get_rl_org_group_list.dspy | 11 + wwwroot/api/get_rl_vendor_config_list.dspy | 11 + wwwroot/api/get_status_list.dspy | 9 + wwwroot/api/get_upapp_list.dspy | 16 + wwwroot/api/get_vendor_list.dspy | 14 + wwwroot/api/rl_asset_create.dspy | 24 +- wwwroot/api/rl_asset_delete.dspy | 22 +- wwwroot/api/rl_asset_group_create.dspy | 20 +- wwwroot/api/rl_asset_group_delete.dspy | 23 +- wwwroot/api/rl_callback.dspy | 9 +- wwwroot/api/rl_vendor_config_create.dspy | 29 +- wwwroot/api/rl_vendor_config_update.dspy | 22 +- wwwroot/api/rl_verify.dspy | 11 +- wwwroot/api/sync_asset_status.dspy | 16 +- wwwroot/api/sync_assets.dspy | 20 +- wwwroot/api/sync_from_vendor.dspy | 23 +- wwwroot/asset_manage.ui | 6 +- wwwroot/create_validate.ui | 169 +++++--- wwwroot/group_manage.ui | 10 +- wwwroot/index.ui | 172 +++++++- wwwroot/org_group_manage.ui | 25 ++ wwwroot/sync_groups.ui | 81 ++-- wwwroot/upload_asset.ui | 182 +++++--- wwwroot/vendor_config_edit.ui | 155 +++++++ wwwroot/vendor_config_manage.ui | 25 ++ 31 files changed, 1108 insertions(+), 519 deletions(-) create mode 100644 wwwroot/api/get_asset_type_list.dspy create mode 100644 wwwroot/api/get_rl_org_group_list.dspy create mode 100644 wwwroot/api/get_rl_vendor_config_list.dspy create mode 100644 wwwroot/api/get_status_list.dspy create mode 100644 wwwroot/api/get_upapp_list.dspy create mode 100644 wwwroot/api/get_vendor_list.dspy create mode 100644 wwwroot/org_group_manage.ui create mode 100644 wwwroot/vendor_config_edit.ui create mode 100644 wwwroot/vendor_config_manage.ui diff --git a/json/rl_org_group_list.json b/json/rl_org_group_list.json index 94fc4ee..617a0fc 100644 --- a/json/rl_org_group_list.json +++ b/json/rl_org_group_list.json @@ -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')}}", diff --git a/json/rl_vendor_config_list.json b/json/rl_vendor_config_list.json index 17eba70..c311128 100644 --- a/json/rl_vendor_config_list.json +++ b/json/rl_vendor_config_list.json @@ -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"}, diff --git a/models/rl_org_group.json b/models/rl_org_group.json index 3839481..3d410e4 100644 --- a/models/rl_org_group.json +++ b/models/rl_org_group.json @@ -51,6 +51,11 @@ "name": "create_time", "title": "创建时间", "type": "datetime" + }, + { + "name": "update_time", + "title": "更新时间", + "type": "datetime" } ], "indexes": [ diff --git a/models/rl_vendor_config.json b/models/rl_vendor_config.json index 9549e78..a3ab882 100644 --- a/models/rl_vendor_config.json +++ b/models/rl_vendor_config.json @@ -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": [] } diff --git a/reallife_asset/init.py b/reallife_asset/init.py index 15d46e3..e9894ba 100644 --- a/reallife_asset/init.py +++ b/reallife_asset/init.py @@ -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"供应商服务已停用"} - - 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} +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", "") + 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"): - """User proxy: Verify vendor config -> Call vendor -> Save org-group mapping.""" - 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调用失败")} + 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 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,37 +515,32 @@ 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", "") - if not vendor_asset_id and "error" not in result: - r = result.get("Result", {}) - vendor_asset_id = r.get("Id", "") + vendor_asset_id = result.get("Id", result.get("Result", {}).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 "" - + async with db.sqlorContext(dbname) as sor: await sor.I("rl_asset", { "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, @@ -757,13 +725,12 @@ def load_reallife_asset(): g.rl_delete_group = rl_delete_group g.rl_sync_group_from_vendor = rl_sync_group_from_vendor g.rl_sync_assets_from_vendor = rl_sync_assets_from_vendor - + # 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 g.rl_query_groups = rl_query_groups - + return True diff --git a/wwwroot/api/get_asset_type_list.dspy b/wwwroot/api/get_asset_type_list.dspy new file mode 100644 index 0000000..458d819 --- /dev/null +++ b/wwwroot/api/get_asset_type_list.dspy @@ -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) diff --git a/wwwroot/api/get_rl_org_group_list.dspy b/wwwroot/api/get_rl_org_group_list.dspy new file mode 100644 index 0000000..785f069 --- /dev/null +++ b/wwwroot/api/get_rl_org_group_list.dspy @@ -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"} diff --git a/wwwroot/api/get_rl_vendor_config_list.dspy b/wwwroot/api/get_rl_vendor_config_list.dspy new file mode 100644 index 0000000..96a3cbf --- /dev/null +++ b/wwwroot/api/get_rl_vendor_config_list.dspy @@ -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"} diff --git a/wwwroot/api/get_status_list.dspy b/wwwroot/api/get_status_list.dspy new file mode 100644 index 0000000..fbb15eb --- /dev/null +++ b/wwwroot/api/get_status_list.dspy @@ -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) diff --git a/wwwroot/api/get_upapp_list.dspy b/wwwroot/api/get_upapp_list.dspy new file mode 100644 index 0000000..6204ae3 --- /dev/null +++ b/wwwroot/api/get_upapp_list.dspy @@ -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) diff --git a/wwwroot/api/get_vendor_list.dspy b/wwwroot/api/get_vendor_list.dspy new file mode 100644 index 0000000..f9a9de5 --- /dev/null +++ b/wwwroot/api/get_vendor_list.dspy @@ -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) diff --git a/wwwroot/api/rl_asset_create.dspy b/wwwroot/api/rl_asset_create.dspy index aa00e09..488c3d0 100644 --- a/wwwroot/api/rl_asset_create.dspy +++ b/wwwroot/api/rl_asset_create.dspy @@ -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" + } +}) diff --git a/wwwroot/api/rl_asset_delete.dspy b/wwwroot/api/rl_asset_delete.dspy index 13a491a..8c527a7 100644 --- a/wwwroot/api/rl_asset_delete.dspy +++ b/wwwroot/api/rl_asset_delete.dspy @@ -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" + } +}) diff --git a/wwwroot/api/rl_asset_group_create.dspy b/wwwroot/api/rl_asset_group_create.dspy index 8e33f81..0ee1143 100644 --- a/wwwroot/api/rl_asset_group_create.dspy +++ b/wwwroot/api/rl_asset_group_create.dspy @@ -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" + } +}) diff --git a/wwwroot/api/rl_asset_group_delete.dspy b/wwwroot/api/rl_asset_group_delete.dspy index c34e555..006f5ef 100644 --- a/wwwroot/api/rl_asset_group_delete.dspy +++ b/wwwroot/api/rl_asset_group_delete.dspy @@ -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" + } +}) diff --git a/wwwroot/api/rl_callback.dspy b/wwwroot/api/rl_callback.dspy index 2d2efd0..6f428d0 100644 --- a/wwwroot/api/rl_callback.dspy +++ b/wwwroot/api/rl_callback.dspy @@ -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 diff --git a/wwwroot/api/rl_vendor_config_create.dspy b/wwwroot/api/rl_vendor_config_create.dspy index 505d98d..1841431 100644 --- a/wwwroot/api/rl_vendor_config_create.dspy +++ b/wwwroot/api/rl_vendor_config_create.dspy @@ -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, diff --git a/wwwroot/api/rl_vendor_config_update.dspy b/wwwroot/api/rl_vendor_config_update.dspy index 56dd3a3..d236c4a 100644 --- a/wwwroot/api/rl_vendor_config_update.dspy +++ b/wwwroot/api/rl_vendor_config_update.dspy @@ -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") diff --git a/wwwroot/api/rl_verify.dspy b/wwwroot/api/rl_verify.dspy index 0228d25..10fbe3d 100644 --- a/wwwroot/api/rl_verify.dspy +++ b/wwwroot/api/rl_verify.dspy @@ -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 diff --git a/wwwroot/api/sync_asset_status.dspy b/wwwroot/api/sync_asset_status.dspy index 12e03c6..939bb6c 100644 --- a/wwwroot/api/sync_asset_status.dspy +++ b/wwwroot/api/sync_asset_status.dspy @@ -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" + } +}) diff --git a/wwwroot/api/sync_assets.dspy b/wwwroot/api/sync_assets.dspy index ee7988a..e36f551 100644 --- a/wwwroot/api/sync_assets.dspy +++ b/wwwroot/api/sync_assets.dspy @@ -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" + } +}) diff --git a/wwwroot/api/sync_from_vendor.dspy b/wwwroot/api/sync_from_vendor.dspy index 8f94939..4df57ee 100644 --- a/wwwroot/api/sync_from_vendor.dspy +++ b/wwwroot/api/sync_from_vendor.dspy @@ -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" + } +}) diff --git a/wwwroot/asset_manage.ui b/wwwroot/asset_manage.ui index 54ea01c..6458816 100644 --- a/wwwroot/asset_manage.ui +++ b/wwwroot/asset_manage.ui @@ -25,9 +25,7 @@ { "widgettype": "Button", "options": { - "label": "上传素材", - "bgcolor": "#1890ff", - "color": "#fff" + "label": "上传素材" }, "binds": [ { @@ -52,4 +50,4 @@ } } ] -} \ No newline at end of file +} diff --git a/wwwroot/create_validate.ui b/wwwroot/create_validate.ui index 59cea27..7aeb3a0 100644 --- a/wwwroot/create_validate.ui +++ b/wwwroot/create_validate.ui @@ -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()" + } + ] + } + ] + } + ] + } + ] } ] -} \ No newline at end of file +} diff --git a/wwwroot/group_manage.ui b/wwwroot/group_manage.ui index 97d0148..e69c568 100644 --- a/wwwroot/group_manage.ui +++ b/wwwroot/group_manage.ui @@ -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": [ { @@ -72,4 +68,4 @@ } } ] -} \ No newline at end of file +} diff --git a/wwwroot/index.ui b/wwwroot/index.ui index 7f7f385..4c27ea3 100644 --- a/wwwroot/index.ui +++ b/wwwroot/index.ui @@ -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" + } + } + ] } ] }, diff --git a/wwwroot/org_group_manage.ui b/wwwroot/org_group_manage.ui new file mode 100644 index 0000000..1f786b2 --- /dev/null +++ b/wwwroot/org_group_manage.ui @@ -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')}}" + } + } + ] +} diff --git a/wwwroot/sync_groups.ui b/wwwroot/sync_groups.ui index 76ae502..81110be 100644 --- a/wwwroot/sync_groups.ui +++ b/wwwroot/sync_groups.ui @@ -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)" + } + ] + } + ] + } + ] } ] -} \ No newline at end of file +} diff --git a/wwwroot/upload_asset.ui b/wwwroot/upload_asset.ui index af48cf2..58c708e 100644 --- a/wwwroot/upload_asset.ui +++ b/wwwroot/upload_asset.ui @@ -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()" + } + ] + } + ] + } + ] + } + ] } ] -} \ No newline at end of file +} diff --git a/wwwroot/vendor_config_edit.ui b/wwwroot/vendor_config_edit.ui new file mode 100644 index 0000000..1b338de --- /dev/null +++ b/wwwroot/vendor_config_edit.ui @@ -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()" + } + ] + } + ] + } + ] + } + ] + } + ] +} diff --git a/wwwroot/vendor_config_manage.ui b/wwwroot/vendor_config_manage.ui new file mode 100644 index 0000000..4a625ba --- /dev/null +++ b/wwwroot/vendor_config_manage.ui @@ -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')}}" + } + } + ] +}