This commit is contained in:
eric
2026-03-10 04:42:05 -05:00
parent d77f90cb8c
commit adeb111282
47 changed files with 1154 additions and 460 deletions

16
app/services/__init__.py Normal file
View File

@@ -0,0 +1,16 @@
"""
共享业务逻辑,基于 SDK 封装
- PocketBase、Memos 通过 app.sdk 使用
- 仅 nomadvip 涉及 Memos
"""
from .pb import get_pb_client
from .memos import create_memos_user
from .payment import handle_payment_success, parse_order_id, processed_orders
__all__ = [
"get_pb_client",
"create_memos_user",
"handle_payment_success",
"parse_order_id",
"processed_orders",
]

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

21
app/services/memos.py Normal file
View File

@@ -0,0 +1,21 @@
"""Memos 用户创建(基于 SDK仅 nomadvip 使用)"""
from app.config import MEMOS_API, MEMOS_TOKEN
from app.sdk.memos import MemosSDK
_memos_sdk: MemosSDK | None = None
def _get_memos_sdk() -> MemosSDK:
global _memos_sdk
if _memos_sdk is None:
_memos_sdk = MemosSDK(
api_url=MEMOS_API,
token=MEMOS_TOKEN,
enabled=bool(MEMOS_API and MEMOS_TOKEN),
)
return _memos_sdk
def create_memos_user(user_id: str, password: str = "123456"):
"""创建 Memos 用户"""
return _get_memos_sdk().create_user(user_id=user_id, password=password)

51
app/services/minio.py Normal file
View File

@@ -0,0 +1,51 @@
"""MinIO 对象存储(基于 SDK"""
from app.config import (
MINIO_ENDPOINT,
MINIO_PORT,
MINIO_ACCESS_KEY,
MINIO_SECRET_KEY,
MINIO_BUCKET,
MINIO_USE_SSL,
MINIO_PUBLIC_URL,
MINIO_UPLOAD_PREFIX,
)
from app.sdk.minio import MinioSDK
_minio_sdk: MinioSDK | None = None
def _get_minio_sdk() -> MinioSDK:
global _minio_sdk
if _minio_sdk is None:
_minio_sdk = MinioSDK(
end_point=MINIO_ENDPOINT,
access_key=MINIO_ACCESS_KEY,
secret_key=MINIO_SECRET_KEY,
bucket=MINIO_BUCKET,
use_ssl=MINIO_USE_SSL,
port=MINIO_PORT,
public_url=MINIO_PUBLIC_URL,
upload_prefix=MINIO_UPLOAD_PREFIX,
)
return _minio_sdk
def get_minio_client():
"""获取 MinIO 客户端(未配置时返回 None"""
sdk = _get_minio_sdk()
if not sdk.enabled:
return None
return sdk.get_client()
def upload_buffer(data: bytes, object_key: str, content_type: str = "application/octet-stream"):
"""上传文件到 MinIO未配置时抛出异常"""
sdk = _get_minio_sdk()
if not sdk.enabled:
raise RuntimeError("MinIO 未配置,请设置 MINIO_ACCESS_KEY、MINIO_SECRET_KEY")
return sdk.upload_buffer(data, object_key, content_type)
def generate_object_key(ext: str) -> str:
"""生成上传用的对象键"""
return _get_minio_sdk().generate_object_key(ext)

86
app/services/payment.py Normal file
View File

@@ -0,0 +1,86 @@
"""支付成功后的统一业务处理(基于 SDK"""
import logging
from .pb import get_pb_client
from .memos import create_memos_user
# 已处理订单(防重复)
processed_orders: set[str] = set()
def parse_order_id(order_id: str) -> tuple[str, str]:
"""从 order_id 解析 user_id 和 pay_type"""
if "_order_" not in order_id:
return "", "unknown"
prefix = order_id.split("_order_")[0]
if "_" in prefix:
parts = prefix.rsplit("_", 1)
return parts[0], parts[1].lower()
return prefix, "unknown"
def handle_payment_success(
order_id: str,
pay_price: str,
provider_name: str,
*,
use_memos: bool = False,
):
"""
支付成功后的统一业务处理
:param use_memos: 是否创建 Memos 用户,仅 nomadvip 为 True
"""
user_id, pay_type = parse_order_id(order_id)
if not user_id:
return
print(f"支付成功 [{provider_name}] 用户ID: {user_id},类型: {pay_type},订单: {order_id}")
if pay_type == "vip" and use_memos:
create_memos_user(user_id)
if pay_type == "meetup":
logging.info(f"meetup 类型支付成功,不创建 memos 用户 - user_id: {user_id}")
valid_types = ["vip", "book", "meetup", "video"]
if pay_type not in valid_types:
pay_type = "vip"
amount_cents = int(float(pay_price or 0) * 100)
try:
pb_client = get_pb_client()
if pay_type == "meetup":
solan = pb_client.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_cents,
})
logging.info(f"meetup 支付字段补齐成功 - user_id: {user_id}, record_id: {record_id}")
else:
solan.create({
"user_id": user_id,
"type": "meetup",
"order_id": order_id,
"amount": amount_cents,
})
logging.info(f"meetup 完整记录创建(兜底) - user_id: {user_id}")
else:
pb_client.collection("payments").create({
"user_id": user_id,
"type": pay_type,
"order_id": order_id,
"amount": amount_cents,
})
processed_orders.add(order_id)
except Exception as e:
print(f"PocketBase 操作失败: {e}")
logging.error(f"PocketBase 操作失败 - order_id: {order_id}, error: {e}")
raise

24
app/services/pb.py Normal file
View File

@@ -0,0 +1,24 @@
"""PocketBase 客户端(基于 SDK"""
from app.config import PB_URL, PB_ADMIN_EMAIL, PB_ADMIN_PASSWORD
from app.sdk.pocketbase import PocketBaseSDK
_pb_sdk: PocketBaseSDK | None = None
def _get_pb_sdk() -> PocketBaseSDK:
global _pb_sdk
if _pb_sdk is None:
_pb_sdk = PocketBaseSDK(
url=PB_URL,
admin_email=PB_ADMIN_EMAIL,
admin_password=PB_ADMIN_PASSWORD,
)
return _pb_sdk
def get_pb_client():
"""获取已认证的 PocketBase 客户端"""
sdk = _get_pb_sdk()
client = sdk.get_client()
print("PocketBase admin 登录成功")
return client