refactor: 移除uapi依赖,改用直接V4签名调用火山引擎API
- 新增 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
This commit is contained in:
parent
af3368c019
commit
414d0e66ed
@ -13,6 +13,8 @@
|
|||||||
"api_mapping": {"title": "API映射", "widgettype": "Text"},
|
"api_mapping": {"title": "API映射", "widgettype": "Text"},
|
||||||
"status": {"title": "状态", "widgettype": "Text"},
|
"status": {"title": "状态", "widgettype": "Text"},
|
||||||
"callback_url": {"title": "回调URL", "widgettype": "Text"},
|
"callback_url": {"title": "回调URL", "widgettype": "Text"},
|
||||||
|
"ak": {"title": "Access Key", "widgettype": "Text"},
|
||||||
|
"sk": {"title": "Secret Key", "widgettype": "Text"},
|
||||||
"create_time": {"title": "创建时间", "widgettype": "Text"},
|
"create_time": {"title": "创建时间", "widgettype": "Text"},
|
||||||
"update_time": {"title": "更新时间", "widgettype": "Text"}
|
"update_time": {"title": "更新时间", "widgettype": "Text"}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -53,6 +53,18 @@
|
|||||||
"type": "str",
|
"type": "str",
|
||||||
"length": 500
|
"length": 500
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "ak",
|
||||||
|
"title": "Access Key",
|
||||||
|
"type": "str",
|
||||||
|
"length": 200
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "sk",
|
||||||
|
"title": "Secret Key",
|
||||||
|
"type": "str",
|
||||||
|
"length": 500
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "create_time",
|
"name": "create_time",
|
||||||
"title": "创建时间",
|
"title": "创建时间",
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
"""
|
"""
|
||||||
reallife_asset module - Real Person Portrait Asset Management.
|
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
|
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
|
import json
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
@ -13,7 +13,7 @@ from ahserver.serverenv import ServerEnv
|
|||||||
from appPublic.log import debug, exception, error
|
from appPublic.log import debug, exception, error
|
||||||
from appPublic.uniqueID import getID
|
from appPublic.uniqueID import getID
|
||||||
from appPublic.dictObject import DictObject
|
from appPublic.dictObject import DictObject
|
||||||
from uapi.uapi import UpAppApi
|
from .rl_volcengine_client import call_volcengine_api
|
||||||
|
|
||||||
MODULE_NAME = "reallife_asset"
|
MODULE_NAME = "reallife_asset"
|
||||||
|
|
||||||
@ -24,7 +24,7 @@ def _get_dbname():
|
|||||||
|
|
||||||
|
|
||||||
async def _get_vendor_config(vendor):
|
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()
|
dbname = _get_dbname()
|
||||||
db = DBPools()
|
db = DBPools()
|
||||||
async with db.sqlorContext(dbname) as sor:
|
async with db.sqlorContext(dbname) as sor:
|
||||||
@ -40,49 +40,27 @@ async def _get_vendor_config(vendor):
|
|||||||
api_mapping = {}
|
api_mapping = {}
|
||||||
return {
|
return {
|
||||||
"success": True,
|
"success": True,
|
||||||
"upappid": rec.upappid,
|
|
||||||
"api_mapping": api_mapping,
|
"api_mapping": api_mapping,
|
||||||
"callback_url": getattr(rec, "callback_url", ""),
|
"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={}):
|
async def _call_vendor(vendor, operation, params={}):
|
||||||
"""
|
"""
|
||||||
Call vendor API via uapi gateway.
|
Call vendor API with V4 signing.
|
||||||
Looks up upappid + apiname from rl_vendor_config, then calls UpAppApi.
|
Reads AK/SK and api_mapping from rl_vendor_config.
|
||||||
Returns parsed dict from response.
|
Returns parsed dict from response.
|
||||||
"""
|
"""
|
||||||
cfg = await _get_vendor_config(vendor)
|
cfg = await _get_vendor_config(vendor)
|
||||||
if not cfg.get("success"):
|
if not cfg.get("success"):
|
||||||
return cfg
|
return cfg
|
||||||
|
|
||||||
upappid = cfg["upappid"]
|
api_mapping = cfg["api_mapping"]
|
||||||
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:
|
try:
|
||||||
uapi = UpAppApi()
|
result = await call_volcengine_api(vendor, operation, params, api_mapping)
|
||||||
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
|
return result
|
||||||
except Exception as e:
|
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)}
|
return {"error": str(e)}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
153
reallife_asset/rl_volcengine_client.py
Normal file
153
reallife_asset/rl_volcengine_client.py
Normal file
@ -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)}
|
||||||
@ -1,41 +1,32 @@
|
|||||||
-- ============================================================
|
-- ============================================================
|
||||||
-- 火山引擎真人人像素材 — 模块配置 SQL
|
-- 火山引擎真人人像素材 — 模块配置 SQL
|
||||||
-- 执行顺序:先执行 uapi_volcengine_ark.sql,再执行本文件
|
-- AK/SK 直接存储在 rl_vendor_config 表中,不再使用 uapi
|
||||||
-- ============================================================
|
-- ============================================================
|
||||||
|
|
||||||
-- 1. 供应商配置 (rl_vendor_config)
|
-- 1. 供应商配置 (rl_vendor_config)
|
||||||
-- api_mapping: 内部操作名 → uapi.name
|
-- api_mapping: 内部操作名 → Volcengine API Action
|
||||||
INSERT INTO rl_vendor_config (id, vendor, vendor_title, upappid, api_mapping, status, callback_url, create_time, update_time) VALUES (
|
-- 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',
|
'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',
|
'active',
|
||||||
'https://token.opencomputing.cn/reallife_asset/api/rl_callback.dspy',
|
'https://token.opencomputing.cn/reallife_asset/api/rl_callback.dspy',
|
||||||
|
'AKLTZWE5YTY1MDRhMmIyNGFlN2JkMzBjN2U0NGFkMWQ5ODM',
|
||||||
|
password_encode('TURFMU9ESTBNamc1TW1JMk5HVmpORGczT1dNeE0yVTRabVV4TVRJeFpUWQ=='),
|
||||||
NOW(),
|
NOW(),
|
||||||
NOW()
|
NOW()
|
||||||
) ON DUPLICATE KEY UPDATE
|
) ON DUPLICATE KEY UPDATE
|
||||||
vendor_title=VALUES(vendor_title),
|
vendor_title=VALUES(vendor_title),
|
||||||
upappid=VALUES(upappid),
|
|
||||||
api_mapping=VALUES(api_mapping),
|
api_mapping=VALUES(api_mapping),
|
||||||
callback_url=VALUES(callback_url),
|
callback_url=VALUES(callback_url),
|
||||||
|
ak=VALUES(ak),
|
||||||
|
sk=VALUES(sk),
|
||||||
update_time=NOW();
|
update_time=NOW();
|
||||||
|
|
||||||
-- 2. API密钥 (upappkey)
|
-- 2. 供应商下拉代码 (appcodes_kv)
|
||||||
-- 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)
|
|
||||||
-- rl_org_group 表的 vendor 字段引用此代码表
|
-- rl_org_group 表的 vendor 字段引用此代码表
|
||||||
INSERT INTO appcodes_kv (parentid, k, v) VALUES
|
INSERT INTO appcodes_kv (parentid, k, v) VALUES
|
||||||
('rl_vendor', 'volcengine', '火山引擎')
|
('rl_vendor', 'volcengine', '火山引擎')
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user