ComfyUI-Ranking-API / open_transaction.py
ZHIWEI666's picture
Upload 30 files
2411edd verified
Raw History Blame Contribute Delete
4.95 kB
"""开放资金接口的实时插件授权与钱包写锁;事务由调用方提交/回滚。"""
import hmac
import hashlib
import json
import re
from decimal import Decimal
from fastapi import HTTPException
from models_sql import RegisteredPlugin, OpenApiOperation, Transaction
from plugin_permissions import parse_permissions
from tip_transaction import lock_wallet_accounts
def validate_live_plugin(row, key, permission):
if row is None or not row.enabled:
raise HTTPException(status_code=403, detail="插件已停用或不存在")
if not isinstance(row.plugin_key, str) or not isinstance(key, str) or not hmac.compare_digest(row.plugin_key.encode('utf-8'), key.encode('utf-8')):
raise HTTPException(status_code=403, detail="Plugin Key 已失效")
try:
permissions = parse_permissions(row.permissions)
except Exception:
raise HTTPException(status_code=503, detail="插件权限配置暂不可用")
if permission not in permissions:
raise HTTPException(status_code=403, detail="插件无权限调用该接口")
return row
def lock_open_wallet(session, account, plugin_id, key, permission):
# 首项资金SQL取得SQLite写事务;其它方言先锁钱包再锁插件,所有开放资金路径同序。
wallet = lock_wallet_accounts(session, (account,))[account]
row = session.query(RegisteredPlugin).filter(RegisteredPlugin.plugin_id == plugin_id).populate_existing().with_for_update().first()
validate_live_plugin(row, key, permission)
return wallet, row
def positive_integer(value):
if isinstance(value, bool) or not isinstance(value, int) or value <= 0:
raise HTTPException(status_code=503, detail="插件额度配置暂不可用")
return value
def quota_total(value):
"""SQL聚合可返回整数或精确Decimal;拒绝浮点/字符串/非整数与坏统计。"""
if isinstance(value, Decimal) and value.is_finite() and value >= 0 and value == value.to_integral_value():
return int(value)
if isinstance(value, bool) or not isinstance(value, int) or value < 0:
raise ValueError("invalid quota history")
return value
def open_operation_identity(request_id, account, plugin_id, kind, payload):
if request_id is None:
return None
if not isinstance(request_id, str) or re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9._:-]{7,127}", request_id) is None:
raise HTTPException(status_code=422, detail="OPEN_REQUEST_ID_INVALID")
encode = lambda value: json.dumps(value, ensure_ascii=False, separators=(',', ':'), allow_nan=False).encode('utf-8')
key = hashlib.sha256(encode([plugin_id, account, kind, request_id])).hexdigest()
fingerprint = hashlib.sha256(encode(payload)).hexdigest()
return key, fingerprint
def read_open_operation(db, identity, account, plugin_id, kind, amount, reference_id):
if identity is None:
return None
key, fingerprint = identity
row = db.get(OpenApiOperation, key)
if row is None:
if db.get(Transaction, f'{kind}_{key}') is not None:
raise HTTPException(status_code=503, detail="OPEN_OPERATION_UNAVAILABLE")
return None
if row.request_hash != fingerprint:
raise HTTPException(status_code=409, detail="OPEN_REQUEST_CONFLICT")
try:
response = json.loads(row.response_payload)
transaction = db.get(Transaction, row.transaction_id)
if (not isinstance(response, dict) or set(response) != {'success', 'remaining_balance', 'transaction_id'}
or response['success'] is not True or type(response['remaining_balance']) is not int
or response['transaction_id'] != row.transaction_id or row.transaction_id != f'{kind}_{key}'
or transaction is None or transaction.account != account or transaction.tx_type != kind
or transaction.source_plugin != plugin_id or transaction.related_account != plugin_id
or type(transaction.amount) is not int or transaction.amount != amount
or transaction.item_id != reference_id
or not isinstance(transaction.prev_hash, str)
or transaction.tx_hash != hashlib.sha256(
f"{transaction.tx_id}{account}{kind}{amount}{transaction.prev_hash}".encode()).hexdigest()):
raise ValueError('invalid open operation')
except Exception:
raise HTTPException(status_code=503, detail="OPEN_OPERATION_UNAVAILABLE")
return response
def save_open_operation(db, identity, response):
if identity is not None:
key, fingerprint = identity
db.add(OpenApiOperation(request_key=key, request_hash=fingerprint,
transaction_id=response['transaction_id'],
response_payload=json.dumps(response, ensure_ascii=False, separators=(',', ':'), allow_nan=False)))