This commit is contained in:
eric
2026-03-15 19:12:12 -05:00
parent 7d57cdc503
commit d44417d10a
42 changed files with 3953 additions and 3 deletions

20
app/sdk/__init__.py Normal file
View File

@@ -0,0 +1,20 @@
"""
PayJS API SDK 模块
- payment: 支付网关ZPAY/XorPay
- pocketbase: PocketBase 数据库
- minio: MinIO 对象存储
- memos: Memos 用户(仅 nomadvip 使用)
"""
from .payment import get_payment_provider, ZPayProvider, XorPayProvider
from .pocketbase import PocketBaseSDK
from .minio import MinioSDK
from .memos import MemosSDK
__all__ = [
"get_payment_provider",
"ZPayProvider",
"XorPayProvider",
"PocketBaseSDK",
"MinioSDK",
"MemosSDK",
]

95
app/sdk/memos.py Normal file
View File

@@ -0,0 +1,95 @@
"""
Memos SDK
用户创建等操作,仅 nomadvip 使用
"""
import logging
from typing import Optional
import requests
class MemosSDK:
"""Memos 用户管理 SDK"""
def __init__(
self,
api_url: str,
token: str,
enabled: bool = True,
):
self.api_url = api_url.rstrip("/")
self.token = token
self.enabled = enabled and bool(api_url and token)
def create_user(
self,
user_id: str,
password: str = "12345678",
) -> dict:
"""
创建 Memos 用户
:param user_id: PocketBase 用户 ID如 user1766043770158
:return: {"success": bool, "data"?: dict, "error"?: str}
"""
if not self.enabled:
return {"success": False, "error": "Memos 未启用"}
try:
if user_id.startswith("user_"):
cleaned = "user" + user_id.split("user_", 1)[1]
elif "_" in user_id:
cleaned = user_id.replace("_", "")
else:
cleaned = user_id
username = cleaned
data = {
"username": username,
"password": password,
"role": "USER",
"state": "NORMAL",
}
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {self.token}",
}
resp = requests.post(
self.api_url,
json=data,
headers=headers,
timeout=10,
)
if resp.status_code in (200, 201):
logging.info(f"memos 用户创建成功: {username}")
return {"success": True, "data": resp.json()}
else:
logging.error(f"memos 创建失败 - username: {username}, status: {resp.status_code}, response: {resp.text}")
return {"success": False, "error": resp.text}
except Exception as e:
logging.error(f"创建 memos 用户异常 - user_id: {user_id}, error: {e}")
return {"success": False, "error": str(e)}
def create_user_by_username(
self,
username: str,
password: str = "12345678",
) -> dict:
"""按用户名直接创建(用于 /create_user 接口)"""
if not self.enabled:
return {"success": False, "error": "Memos 未启用"}
try:
data = {
"username": username,
"password": password,
"role": "USER",
"state": "NORMAL",
}
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {self.token}",
}
resp = requests.post(self.api_url, json=data, headers=headers, timeout=10)
return resp.json()
except Exception as e:
logging.error(f"创建 memos 用户异常 - username: {username}, error: {e}")
return {"error": str(e)}

92
app/sdk/minio.py Normal file
View File

@@ -0,0 +1,92 @@
"""
MinIO SDK
对象存储封装,支持配置注入
"""
import os
import time
import random
import string
from typing import Optional
try:
from minio import Minio
MINIO_AVAILABLE = True
except ImportError:
MINIO_AVAILABLE = False
Minio = None
class MinioSDK:
"""MinIO 对象存储 SDK"""
def __init__(
self,
end_point: str,
access_key: str,
secret_key: str,
bucket: str = "hackrobot",
use_ssl: bool = True,
port: Optional[int] = None,
region: str = "china",
public_url: Optional[str] = None,
upload_prefix: str = "joins",
):
if not MINIO_AVAILABLE:
raise ImportError("minio 未安装,请执行: pip install minio")
self.end_point = end_point
self.port = port or (443 if use_ssl else 80)
self.access_key = access_key
self.secret_key = secret_key
self.bucket = bucket
self.use_ssl = use_ssl
self.region = region
self.public_url = public_url or f"https://{end_point}/{bucket}"
self.upload_prefix = upload_prefix.rstrip("/")
self._client: Optional[Minio] = None
def get_client(self) -> "Minio":
"""获取 MinIO 客户端"""
if self._client is None:
endpoint = f"{self.end_point}:{self.port}" if self.port not in (80, 443) else self.end_point
self._client = Minio(
endpoint,
access_key=self.access_key,
secret_key=self.secret_key,
secure=self.use_ssl,
region=self.region,
)
return self._client
def generate_object_key(self, ext: str) -> str:
"""生成上传用的对象键"""
ext = ext.lstrip(".")
suffix = "".join(random.choices(string.ascii_lowercase + string.digits, k=8))
return f"{self.upload_prefix}/{int(time.time() * 1000)}_{suffix}.{ext}"
def upload_buffer(
self,
data: bytes,
object_key: str,
content_type: str = "application/octet-stream",
) -> dict:
"""
上传文件到 MinIO
:return: {"url": str, "object_key": str}
"""
client = self.get_client()
from io import BytesIO
stream = BytesIO(data)
client.put_object(
self.bucket,
object_key,
stream,
len(data),
content_type=content_type,
)
url = f"{self.public_url.rstrip('/')}/{object_key}"
return {"url": url, "object_key": object_key}
@property
def enabled(self) -> bool:
"""是否启用(需配置 access_key/secret_key"""
return bool(self.end_point and self.access_key and self.secret_key)

View File

@@ -0,0 +1,15 @@
"""
支付网关 SDK
支持 ZPAY、XorPay通过配置切换
"""
from app.payment.base import PaymentProvider
from app.payment.zpay import ZPayProvider
from app.payment.xorpay import XorPayProvider
from app.payment.factory import get_payment_provider
__all__ = [
"PaymentProvider",
"ZPayProvider",
"XorPayProvider",
"get_payment_provider",
]

69
app/sdk/pocketbase.py Normal file
View File

@@ -0,0 +1,69 @@
"""
PocketBase SDK
数据库操作封装,支持配置注入
"""
from typing import Optional
from pocketbase import PocketBase
class PocketBaseSDK:
"""PocketBase 客户端 SDK"""
def __init__(
self,
url: str,
admin_email: str,
admin_password: str,
):
self.url = url
self.admin_email = admin_email
self.admin_password = admin_password
self._client: Optional[PocketBase] = None
def get_client(self) -> PocketBase:
"""获取已认证的 PocketBase 客户端"""
if self._client is None:
self._client = PocketBase(self.url)
self._client.admins.auth_with_password(self.admin_email, self.admin_password)
return self._client
def collection(self, name: str):
"""获取集合"""
return self.get_client().collection(name)
def create_payment(self, user_id: str, pay_type: str, order_id: str, amount: int) -> dict:
"""创建支付记录"""
return self.collection("payments").create({
"user_id": user_id,
"type": pay_type,
"order_id": order_id,
"amount": amount,
})
def update_meetup_payment(
self,
user_id: str,
order_id: str,
amount: int,
) -> None:
"""更新或创建 meetup 支付记录"""
solan = self.collection("solan")
existing = solan.get_list(1, 1, {
"filter": f'user_id = "{user_id}"',
"sort": "-created",
})
if existing.items:
record_id = existing.items[0].id
solan.update(record_id, {
"type": "meetup",
"order_id": order_id,
"amount": amount,
})
else:
solan.create({
"user_id": user_id,
"type": "meetup",
"order_id": order_id,
"amount": amount,
})