From 414d0e66ed8eb0ef745618a579033ab1273e3141 Mon Sep 17 00:00:00 2001 From: yumoqing Date: Fri, 29 May 2026 14:13:47 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E7=A7=BB=E9=99=A4uapi=E4=BE=9D?= =?UTF-8?q?=E8=B5=96=EF=BC=8C=E6=94=B9=E7=94=A8=E7=9B=B4=E6=8E=A5V4?= =?UTF-8?q?=E7=AD=BE=E5=90=8D=E8=B0=83=E7=94=A8=E7=81=AB=E5=B1=B1=E5=BC=95?= =?UTF-8?q?=E6=93=8EAPI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 rl_volcengine_client.py: V4 HMAC-SHA256签名 + StreamHttpClient - rl_vendor_config 表新增 ak/sk 字段,AK/SK直接存储(不再经过uapi/upappkey) - init.py: _call_vendor 改用 rl_volcengine_client.call_volcengine_api - api_mapping 改为直接映射Volcengine API Action(如CreateAsset) - SQL: 移除upappkey部分,ak/sk存入rl_vendor_config --- json/rl_vendor_config_list.json | 2 + models/rl_vendor_config.json | 12 ++ reallife_asset/init.py | 40 ++----- reallife_asset/rl_volcengine_client.py | 153 +++++++++++++++++++++++++ scripts/vendor_config_volcengine.sql | 31 ++--- 5 files changed, 187 insertions(+), 51 deletions(-) create mode 100644 reallife_asset/rl_volcengine_client.py diff --git a/json/rl_vendor_config_list.json b/json/rl_vendor_config_list.json index c311128..27ee195 100644 --- a/json/rl_vendor_config_list.json +++ b/json/rl_vendor_config_list.json @@ -13,6 +13,8 @@ "api_mapping": {"title": "API映射", "widgettype": "Text"}, "status": {"title": "状态", "widgettype": "Text"}, "callback_url": {"title": "回调URL", "widgettype": "Text"}, + "ak": {"title": "Access Key", "widgettype": "Text"}, + "sk": {"title": "Secret Key", "widgettype": "Text"}, "create_time": {"title": "创建时间", "widgettype": "Text"}, "update_time": {"title": "更新时间", "widgettype": "Text"} }, diff --git a/models/rl_vendor_config.json b/models/rl_vendor_config.json index a3ab882..9a9c21c 100644 --- a/models/rl_vendor_config.json +++ b/models/rl_vendor_config.json @@ -53,6 +53,18 @@ "type": "str", "length": 500 }, + { + "name": "ak", + "title": "Access Key", + "type": "str", + "length": 200 + }, + { + "name": "sk", + "title": "Secret Key", + "type": "str", + "length": 500 + }, { "name": "create_time", "title": "创建时间", diff --git a/reallife_asset/init.py b/reallife_asset/init.py index e9894ba..27bf7c3 100644 --- a/reallife_asset/init.py +++ b/reallife_asset/init.py @@ -1,8 +1,8 @@ """ reallife_asset module - Real Person Portrait Asset Management. -Supports multiple vendors via Sage uapi gateway. +Supports multiple vendors via direct API calls with V4 signing. Vendor API routing: rl_vendor_config.api_mapping JSON maps internal -operations to uapi apinames. Each vendor has its own upappid. +operations to API action names. AK/SK stored in rl_vendor_config. """ import json from datetime import datetime @@ -13,7 +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 uapi.uapi import UpAppApi +from .rl_volcengine_client import call_volcengine_api MODULE_NAME = "reallife_asset" @@ -24,7 +24,7 @@ def _get_dbname(): async def _get_vendor_config(vendor): - """Look up vendor config: upappid + api_mapping from rl_vendor_config.""" + """Look up vendor config: api_mapping + callback_url from rl_vendor_config.""" dbname = _get_dbname() db = DBPools() async with db.sqlorContext(dbname) as sor: @@ -40,49 +40,27 @@ async def _get_vendor_config(vendor): 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. + Call vendor API with V4 signing. + Reads AK/SK and api_mapping from rl_vendor_config. 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 + api_mapping = cfg["api_mapping"] 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 {} + result = await call_volcengine_api(vendor, operation, params, api_mapping) return result except Exception as e: - error(f"_call_vendor {vendor}/{operation} error: {e}\\n{format_exc()}") + error(f"_call_vendor {vendor}/{operation} error: {e}\n{format_exc()}") return {"error": str(e)} diff --git a/reallife_asset/rl_volcengine_client.py b/reallife_asset/rl_volcengine_client.py new file mode 100644 index 0000000..527b66e --- /dev/null +++ b/reallife_asset/rl_volcengine_client.py @@ -0,0 +1,153 @@ +""" +Volcengine Ark API Client for Real Person Portrait Asset Management. +Implements V4 HMAC-SHA256 signing. +Uses StreamHttpClient for HTTP requests. +Reads AK/SK from rl_vendor_config table. +""" +import json +import hashlib +import hmac +import datetime +from appPublic.log import debug, error +from appPublic.streamhttpclient import StreamHttpClient +from ahserver.serverenv import ServerEnv + +SERVICE = "ark" +REGION = "cn-beijing" +VERSION = "2024-01-01" +HOST = "open.volcengineapi.com" +BASE_URL = f"https://{HOST}" + + +def _sign(key, msg): + """HMAC-SHA256 sign.""" + return hmac.new(key, msg.encode("utf-8"), hashlib.sha256).digest() + + +def _get_signature_key(secret_key, date_stamp, region, service): + """Derive the signing key.""" + k_date = _sign(secret_key.encode("utf-8"), date_stamp) + k_region = _sign(k_date, region) + k_service = _sign(k_region, service) + k_signing = _sign(k_service, "request") + return k_signing + + +async def call_volcengine_api(vendor, operation, params, api_mapping): + """ + Call Volcengine Ark API with V4 signing. + + Args: + vendor: vendor identifier (e.g., "volcengine") + operation: internal operation name (e.g., "create_session") + params: API parameters dict + api_mapping: dict mapping operation to API action name + + Returns: + dict: API response or error + """ + # Get action name from mapping + action = api_mapping.get(operation) + if not action: + return {"error": f"未配置操作: {operation}"} + + # Read AK/SK from vendor config + from sqlor.dbpools import DBPools + dbname = ServerEnv().get_module_dbname("reallife_asset") + db = DBPools() + async with db.sqlorContext(dbname) as sor: + recs = await sor.R("rl_vendor_config", {"vendor": vendor}) + if not recs: + return {"error": f"供应商配置不存在: {vendor}"} + rec = recs[0] + ak = getattr(rec, "ak", "") + sk_encrypted = getattr(rec, "sk", "") + + if not ak or not sk_encrypted: + return {"error": "AK/SK未配置"} + + # Decrypt SK + env = ServerEnv() + sk = env.password_decode(sk_encrypted) + + # Build signed request + now = datetime.datetime.utcnow() + date_stamp = now.strftime("%Y%m%d") + amz_date = now.strftime("%Y%m%dT%H%M%SZ") + + # Query string with Action and Version + query_params = f"Action={action}&Version={VERSION}" + + # Headers + content_type = "application/json" + payload = json.dumps(params, ensure_ascii=False) + payload_hash = hashlib.sha256(payload.encode("utf-8")).hexdigest() + + headers_to_sign = { + "host": HOST, + "x-date": amz_date, + "x-content-sha256": payload_hash, + "content-type": content_type, + } + signed_headers = ";".join(sorted(headers_to_sign.keys())) + canonical_headers = "".join( + f"{k}:{v}\n" for k, v in sorted(headers_to_sign.items()) + ) + + # Canonical request + canonical_request = "\n".join([ + "POST", + "/", + query_params, + canonical_headers, + signed_headers, + payload_hash, + ]) + + # String to sign + credential_scope = f"{date_stamp}/{REGION}/{SERVICE}/request" + string_to_sign = "\n".join([ + "HMAC-SHA256", + amz_date, + credential_scope, + hashlib.sha256(canonical_request.encode("utf-8")).hexdigest(), + ]) + + # Signing key and signature + signing_key = _get_signature_key(sk, date_stamp, REGION, SERVICE) + signature = hmac.new( + signing_key, string_to_sign.encode("utf-8"), hashlib.sha256 + ).hexdigest() + + # Authorization header + authorization = ( + f"HMAC-SHA256 " + f"Credential={ak}/{credential_scope}, " + f"SignedHeaders={signed_headers}, " + f"Signature={signature}" + ) + + # Build request + url = f"{BASE_URL}/?{query_params}" + req_headers = { + "Host": HOST, + "X-Date": amz_date, + "X-Content-Sha256": payload_hash, + "Content-Type": content_type, + "Authorization": authorization, + } + + # Make HTTP request + try: + hc = StreamHttpClient() + raw = await hc.request("POST", url, headers=req_headers, data=payload.encode("utf-8")) + + # Parse response + if isinstance(raw, bytes): + raw = raw.decode("utf-8") + result = json.loads(raw) if raw else {} + debug(f"volcengine {operation} response: {result}") + return result + except Exception as e: + error(f"volcengine {operation} error: {e}") + return {"error": str(e)} diff --git a/scripts/vendor_config_volcengine.sql b/scripts/vendor_config_volcengine.sql index a6f9e17..d7fd856 100644 --- a/scripts/vendor_config_volcengine.sql +++ b/scripts/vendor_config_volcengine.sql @@ -1,41 +1,32 @@ -- ============================================================ -- 火山引擎真人人像素材 — 模块配置 SQL --- 执行顺序:先执行 uapi_volcengine_ark.sql,再执行本文件 +-- AK/SK 直接存储在 rl_vendor_config 表中,不再使用 uapi -- ============================================================ -- 1. 供应商配置 (rl_vendor_config) --- api_mapping: 内部操作名 → uapi.name -INSERT INTO rl_vendor_config (id, vendor, vendor_title, upappid, api_mapping, status, callback_url, create_time, update_time) VALUES ( +-- api_mapping: 内部操作名 → Volcengine API Action +-- ak/sk: 火山引擎 Access Key / Secret Key (sk加密存储) +INSERT INTO rl_vendor_config (id, vendor, vendor_title, upappid, api_mapping, status, callback_url, ak, sk, create_time, update_time) VALUES ( 'volcengine', 'volcengine', '火山引擎', - 'volcengine_ark', - '{"create_session":"createVisualValidateSession","check_session":"getVisualValidateResult","upload_asset":"createAsset","get_asset":"getAsset","delete_asset":"deleteAsset","delete_group":"deleteAssetGroup","list_groups":"listAssetGroups","list_assets":"listAssets","get_group":"getAssetGroup","update_asset":"updateAsset","update_group":"updateAssetGroup"}', + '', + '{"create_session":"CreateVisualValidateSession","check_session":"GetVisualValidateResult","upload_asset":"CreateAsset","get_asset":"GetAsset","delete_asset":"DeleteAsset","delete_group":"DeleteAssetGroup","list_groups":"ListAssetGroups","list_assets":"ListAssets","get_group":"GetAssetGroup","update_asset":"UpdateAsset","update_group":"UpdateAssetGroup"}', 'active', 'https://token.opencomputing.cn/reallife_asset/api/rl_callback.dspy', + 'AKLTZWE5YTY1MDRhMmIyNGFlN2JkMzBjN2U0NGFkMWQ5ODM', + password_encode('TURFMU9ESTBNamc1TW1JMk5HVmpORGczT1dNeE0yVTRabVV4TVRJeFpUWQ=='), NOW(), NOW() ) ON DUPLICATE KEY UPDATE vendor_title=VALUES(vendor_title), - upappid=VALUES(upappid), api_mapping=VALUES(api_mapping), callback_url=VALUES(callback_url), + ak=VALUES(ak), + sk=VALUES(sk), update_time=NOW(); --- 2. API密钥 (upappkey) --- callerid = vendor = 'volcengine',与 rl_vendor_config.vendor 对应 -INSERT INTO upappkey (id, upappid, ownerid, myappid, apikey, secretkey) VALUES ( - 'volcengine_ark_key', - 'volcengine_ark', - '0', - 'volcengine', - password_encode('AKLTZWE5YTY1MDRhMmIyNGFlN2JkMzBjN2U0NGFkMWQ5ODM'), - password_encode('TURFMU9ESTBNamc1TW1JMk5HVmpORGczT1dNeE0yVTRabVV4TVRJeFpUWQ==') -) ON DUPLICATE KEY UPDATE - apikey=VALUES(apikey), - secretkey=VALUES(secretkey); - --- 3. 供应商下拉代码 (appcodes_kv) +-- 2. 供应商下拉代码 (appcodes_kv) -- rl_org_group 表的 vendor 字段引用此代码表 INSERT INTO appcodes_kv (parentid, k, v) VALUES ('rl_vendor', 'volcengine', '火山引擎')