Spaces:
Running
Running
Upload 8 files
Browse files- router_posts.py +5 -3
- router_prompts.py +9 -5
- router_wallet.py +20 -10
router_posts.py
CHANGED
|
@@ -13,13 +13,13 @@ import 数据库连接 as db
|
|
| 13 |
from comment_store import COMMENT_MAX_LENGTH, append_comment, increment_comment_count, attach_comment_counts, read_comment_view
|
| 14 |
from 安全认证 import require_auth
|
| 15 |
from db_utils import read_public_source, record_view, sort_cache
|
| 16 |
-
from database_sql import get_db
|
| 17 |
from models_sql import Wallet, Transaction
|
| 18 |
from notifications import add_notification
|
| 19 |
from tip_transaction import lock_tip_wallets
|
| 20 |
from tip_store import run_tip_effect
|
| 21 |
import time
|
| 22 |
-
from owned_thread import OwnedThreadGroup, owned_sync_endpoint
|
| 23 |
from post_store import publish_post, edit_post, delete_post as remove_post, PostStoreError
|
| 24 |
import uuid
|
| 25 |
import hashlib
|
|
@@ -36,6 +36,7 @@ start_posts_io = _posts_io.start
|
|
| 36 |
stop_posts_io = _posts_io.stop
|
| 37 |
close_posts_io = _posts_io.close
|
| 38 |
_posts_endpoint = owned_sync_endpoint(_posts_io)
|
|
|
|
| 39 |
|
| 40 |
router = APIRouter()
|
| 41 |
|
|
@@ -426,7 +427,8 @@ def calculate_tx_hash(tx_id, account, tx_type, amount, prev_hash):
|
|
| 426 |
|
| 427 |
@router.post("/api/posts/{post_id}/tip")
|
| 428 |
@shared_limit(10)
|
| 429 |
-
|
|
|
|
| 430 |
"""
|
| 431 |
打赏帖子(原子操作,并发安全)- 使用 SQL Wallet 系统
|
| 432 |
"""
|
|
|
|
| 13 |
from comment_store import COMMENT_MAX_LENGTH, append_comment, increment_comment_count, attach_comment_counts, read_comment_view
|
| 14 |
from 安全认证 import require_auth
|
| 15 |
from db_utils import read_public_source, record_view, sort_cache
|
| 16 |
+
from database_sql import get_db, SessionLocal
|
| 17 |
from models_sql import Wallet, Transaction
|
| 18 |
from notifications import add_notification
|
| 19 |
from tip_transaction import lock_tip_wallets
|
| 20 |
from tip_store import run_tip_effect
|
| 21 |
import time
|
| 22 |
+
from owned_thread import OwnedThreadGroup, owned_sync_endpoint, owned_session_endpoint
|
| 23 |
from post_store import publish_post, edit_post, delete_post as remove_post, PostStoreError
|
| 24 |
import uuid
|
| 25 |
import hashlib
|
|
|
|
| 36 |
stop_posts_io = _posts_io.stop
|
| 37 |
close_posts_io = _posts_io.close
|
| 38 |
_posts_endpoint = owned_sync_endpoint(_posts_io)
|
| 39 |
+
_posts_session_endpoint = owned_session_endpoint(_posts_io, lambda: SessionLocal(), 'db_session')
|
| 40 |
|
| 41 |
router = APIRouter()
|
| 42 |
|
|
|
|
| 427 |
|
| 428 |
@router.post("/api/posts/{post_id}/tip")
|
| 429 |
@shared_limit(10)
|
| 430 |
+
@_posts_session_endpoint
|
| 431 |
+
def tip_post(request: Request, post_id: str, amount: int, is_anon: bool = False, current_user: str = Depends(require_auth), db_session: Session = Depends(get_db)):
|
| 432 |
"""
|
| 433 |
打赏帖子(原子操作,并发安全)- 使用 SQL Wallet 系统
|
| 434 |
"""
|
router_prompts.py
CHANGED
|
@@ -21,14 +21,14 @@ import 数据库连接 as db
|
|
| 21 |
from comment_store import COMMENT_MAX_LENGTH, append_comment, increment_comment_count, attach_comment_counts, read_comment_view
|
| 22 |
from 安全认证 import require_auth, optional_auth, is_admin
|
| 23 |
from db_utils import read_public_source, record_view, sort_cache
|
| 24 |
-
from database_sql import get_db
|
| 25 |
from models_sql import Wallet, Transaction, Ownership
|
| 26 |
from notifications import add_notification
|
| 27 |
from tip_transaction import lock_tip_wallets
|
| 28 |
from purchase_transaction import effective_resource_price, lock_resource_purchase_wallets
|
| 29 |
from tip_store import run_tip_effect
|
| 30 |
import time
|
| 31 |
-
from owned_thread import OwnedThreadGroup, owned_sync_endpoint
|
| 32 |
from prompt_store import (PromptStoreError, publish_prompt, edit_prompt,
|
| 33 |
delete_prompt as remove_prompt, project_price, load_categories, modify_category, read_prompts)
|
| 34 |
import uuid
|
|
@@ -46,6 +46,7 @@ start_prompts_io = _prompts_io.start
|
|
| 46 |
stop_prompts_io = _prompts_io.stop
|
| 47 |
close_prompts_io = _prompts_io.close
|
| 48 |
_prompts_endpoint = owned_sync_endpoint(_prompts_io)
|
|
|
|
| 49 |
|
| 50 |
router = APIRouter()
|
| 51 |
|
|
@@ -340,7 +341,8 @@ def get_my_prompts(current_user: str = Depends(require_auth)):
|
|
| 340 |
|
| 341 |
|
| 342 |
@router.get("/api/prompts/{prompt_id}")
|
| 343 |
-
|
|
|
|
| 344 |
db_session: Session = Depends(get_db)):
|
| 345 |
"""
|
| 346 |
获取提示词详情
|
|
@@ -657,7 +659,8 @@ def toggle_favorite(request: Request, prompt_id: str, current_user: str = Depend
|
|
| 657 |
|
| 658 |
@router.post("/api/prompts/{prompt_id}/tip")
|
| 659 |
@shared_limit(10)
|
| 660 |
-
|
|
|
|
| 661 |
current_user: str = Depends(require_auth), db_session: Session = Depends(get_db)):
|
| 662 |
"""
|
| 663 |
打赏提示词(原子操作,并发安全)- 使用 SQL Wallet 系统
|
|
@@ -823,7 +826,8 @@ async def tip_prompt(request: Request, prompt_id: str, amount: int, is_anon: boo
|
|
| 823 |
|
| 824 |
@router.post("/api/prompts/{prompt_id}/purchase")
|
| 825 |
@shared_limit(10) # 🔒 P0安全优化:购买每分钟最多10次
|
| 826 |
-
|
|
|
|
| 827 |
purchase: Optional[PromptPurchaseRequest] = None,
|
| 828 |
current_user: str = Depends(require_auth), db_session: Session = Depends(get_db)):
|
| 829 |
"""资金与所有权原子提交;通知失败不改变已提交的购买结果。"""
|
|
|
|
| 21 |
from comment_store import COMMENT_MAX_LENGTH, append_comment, increment_comment_count, attach_comment_counts, read_comment_view
|
| 22 |
from 安全认证 import require_auth, optional_auth, is_admin
|
| 23 |
from db_utils import read_public_source, record_view, sort_cache
|
| 24 |
+
from database_sql import get_db, SessionLocal
|
| 25 |
from models_sql import Wallet, Transaction, Ownership
|
| 26 |
from notifications import add_notification
|
| 27 |
from tip_transaction import lock_tip_wallets
|
| 28 |
from purchase_transaction import effective_resource_price, lock_resource_purchase_wallets
|
| 29 |
from tip_store import run_tip_effect
|
| 30 |
import time
|
| 31 |
+
from owned_thread import OwnedThreadGroup, owned_sync_endpoint, owned_session_endpoint
|
| 32 |
from prompt_store import (PromptStoreError, publish_prompt, edit_prompt,
|
| 33 |
delete_prompt as remove_prompt, project_price, load_categories, modify_category, read_prompts)
|
| 34 |
import uuid
|
|
|
|
| 46 |
stop_prompts_io = _prompts_io.stop
|
| 47 |
close_prompts_io = _prompts_io.close
|
| 48 |
_prompts_endpoint = owned_sync_endpoint(_prompts_io)
|
| 49 |
+
_prompts_session_endpoint = owned_session_endpoint(_prompts_io, lambda: SessionLocal(), 'db_session')
|
| 50 |
|
| 51 |
router = APIRouter()
|
| 52 |
|
|
|
|
| 341 |
|
| 342 |
|
| 343 |
@router.get("/api/prompts/{prompt_id}")
|
| 344 |
+
@_prompts_session_endpoint
|
| 345 |
+
def get_prompt_detail(prompt_id: str, current_user: str = Depends(optional_auth),
|
| 346 |
db_session: Session = Depends(get_db)):
|
| 347 |
"""
|
| 348 |
获取提示词详情
|
|
|
|
| 659 |
|
| 660 |
@router.post("/api/prompts/{prompt_id}/tip")
|
| 661 |
@shared_limit(10)
|
| 662 |
+
@_prompts_session_endpoint
|
| 663 |
+
def tip_prompt(request: Request, prompt_id: str, amount: int, is_anon: bool = False,
|
| 664 |
current_user: str = Depends(require_auth), db_session: Session = Depends(get_db)):
|
| 665 |
"""
|
| 666 |
打赏提示词(原子操作,并发安全)- 使用 SQL Wallet 系统
|
|
|
|
| 826 |
|
| 827 |
@router.post("/api/prompts/{prompt_id}/purchase")
|
| 828 |
@shared_limit(10) # 🔒 P0安全优化:购买每分钟最多10次
|
| 829 |
+
@_prompts_session_endpoint
|
| 830 |
+
def purchase_prompt(request: Request, prompt_id: str,
|
| 831 |
purchase: Optional[PromptPurchaseRequest] = None,
|
| 832 |
current_user: str = Depends(require_auth), db_session: Session = Depends(get_db)):
|
| 833 |
"""资金与所有权原子提交;通知失败不改变已提交的购买结果。"""
|
router_wallet.py
CHANGED
|
@@ -16,7 +16,6 @@ from fastapi.responses import Response
|
|
| 16 |
from sqlalchemy.orm import Session
|
| 17 |
from pydantic import BaseModel, Field
|
| 18 |
import time
|
| 19 |
-
import asyncio
|
| 20 |
import uuid
|
| 21 |
import hashlib
|
| 22 |
import os
|
|
@@ -137,7 +136,8 @@ def calculate_tx_hash(tx_id, account, tx_type, amount, prev_hash):
|
|
| 137 |
|
| 138 |
@router.post("/api/wallet/create_recharge_order")
|
| 139 |
@shared_limit(10)
|
| 140 |
-
|
|
|
|
| 141 |
if not alipay:
|
| 142 |
raise HTTPException(status_code=500, detail="支付网关配置错误,请联系管理员")
|
| 143 |
if type(req.amount) is not int or not 1 <= req.amount <= 10000:
|
|
@@ -146,7 +146,7 @@ async def create_recharge_order(request: Request, req: RechargeRequest, current_
|
|
| 146 |
try:
|
| 147 |
# 先持久化;失败时不能生成可付款订单。
|
| 148 |
persist_order(db, order_id, current_user, req.amount)
|
| 149 |
-
result =
|
| 150 |
out_trade_no=order_id, total_amount=str(req.amount),
|
| 151 |
subject=f"ComfyUI Community Points - {current_user}")
|
| 152 |
if not isinstance(result, dict) or result.get("code") != "10000" or not isinstance(result.get("qr_code"), str) or not result["qr_code"]:
|
|
@@ -157,11 +157,9 @@ async def create_recharge_order(request: Request, req: RechargeRequest, current_
|
|
| 157 |
raise HTTPException(status_code=503, detail="创建支付订单失败,请稍后查询确认")
|
| 158 |
|
| 159 |
|
| 160 |
-
@
|
| 161 |
-
|
| 162 |
try:
|
| 163 |
-
form = await request.form()
|
| 164 |
-
pairs = list(form.multi_items())
|
| 165 |
if len({key for key, _ in pairs}) != len(pairs):
|
| 166 |
raise ValueError("duplicate payment fields")
|
| 167 |
data = dict(pairs)
|
|
@@ -169,7 +167,7 @@ async def alipay_notify(request: Request, db: Session = Depends(get_db)):
|
|
| 169 |
sign_type = data.pop("sign_type", None)
|
| 170 |
if not alipay or not isinstance(signature, str) or not signature or sign_type != "RSA2":
|
| 171 |
raise ValueError("invalid signature fields")
|
| 172 |
-
if not
|
| 173 |
raise ValueError("invalid signature")
|
| 174 |
app_id = os.environ.get("ALIPAY_APPID", "").strip()
|
| 175 |
seller_id = os.environ.get("ALIPAY_SELLER_ID", "").strip()
|
|
@@ -191,8 +189,20 @@ async def alipay_notify(request: Request, db: Session = Depends(get_db)):
|
|
| 191 |
return Response(content="fail", media_type="text/plain")
|
| 192 |
|
| 193 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 194 |
@router.get("/api/wallet/check_order/{order_id}")
|
| 195 |
-
|
|
|
|
| 196 |
try:
|
| 197 |
info = read_order(db, order_id)
|
| 198 |
if info is None or info["account"] != current_user:
|
|
@@ -201,7 +211,7 @@ async def check_order(order_id: str, current_user: str = Depends(require_auth),
|
|
| 201 |
return {"status": "SUCCESS"}
|
| 202 |
if not alipay:
|
| 203 |
return {"status": "PENDING"}
|
| 204 |
-
result =
|
| 205 |
if not isinstance(result, dict) or result.get("code") != "10000":
|
| 206 |
return {"status": "PENDING"}
|
| 207 |
if result.get("out_trade_no") != order_id:
|
|
|
|
| 16 |
from sqlalchemy.orm import Session
|
| 17 |
from pydantic import BaseModel, Field
|
| 18 |
import time
|
|
|
|
| 19 |
import uuid
|
| 20 |
import hashlib
|
| 21 |
import os
|
|
|
|
| 136 |
|
| 137 |
@router.post("/api/wallet/create_recharge_order")
|
| 138 |
@shared_limit(10)
|
| 139 |
+
@_wallet_endpoint
|
| 140 |
+
def create_recharge_order(request: Request, req: RechargeRequest, current_user: str = Depends(require_auth), db: Session = Depends(get_db)):
|
| 141 |
if not alipay:
|
| 142 |
raise HTTPException(status_code=500, detail="支付网关配置错误,请联系管理员")
|
| 143 |
if type(req.amount) is not int or not 1 <= req.amount <= 10000:
|
|
|
|
| 146 |
try:
|
| 147 |
# 先持久化;失败时不能生成可付款订单。
|
| 148 |
persist_order(db, order_id, current_user, req.amount)
|
| 149 |
+
result = alipay.api_alipay_trade_precreate(
|
| 150 |
out_trade_no=order_id, total_amount=str(req.amount),
|
| 151 |
subject=f"ComfyUI Community Points - {current_user}")
|
| 152 |
if not isinstance(result, dict) or result.get("code") != "10000" or not isinstance(result.get("qr_code"), str) or not result["qr_code"]:
|
|
|
|
| 157 |
raise HTTPException(status_code=503, detail="创建支付订单失败,请稍后查询确认")
|
| 158 |
|
| 159 |
|
| 160 |
+
@_wallet_endpoint
|
| 161 |
+
def _process_alipay_notification(pairs, db: Session = Depends(get_db)):
|
| 162 |
try:
|
|
|
|
|
|
|
| 163 |
if len({key for key, _ in pairs}) != len(pairs):
|
| 164 |
raise ValueError("duplicate payment fields")
|
| 165 |
data = dict(pairs)
|
|
|
|
| 167 |
sign_type = data.pop("sign_type", None)
|
| 168 |
if not alipay or not isinstance(signature, str) or not signature or sign_type != "RSA2":
|
| 169 |
raise ValueError("invalid signature fields")
|
| 170 |
+
if not alipay.verify(data, signature):
|
| 171 |
raise ValueError("invalid signature")
|
| 172 |
app_id = os.environ.get("ALIPAY_APPID", "").strip()
|
| 173 |
seller_id = os.environ.get("ALIPAY_SELLER_ID", "").strip()
|
|
|
|
| 189 |
return Response(content="fail", media_type="text/plain")
|
| 190 |
|
| 191 |
|
| 192 |
+
@router.post("/api/wallet/alipay_notify")
|
| 193 |
+
async def alipay_notify(request: Request):
|
| 194 |
+
try:
|
| 195 |
+
form = await request.form()
|
| 196 |
+
pairs = list(form.multi_items())
|
| 197 |
+
except Exception as error:
|
| 198 |
+
run_tip_effect(lambda: logger.error(f"RECHARGE_ERROR | error_type={type(error).__name__}"))
|
| 199 |
+
return Response(content="fail", media_type="text/plain")
|
| 200 |
+
return await _process_alipay_notification(pairs)
|
| 201 |
+
|
| 202 |
+
|
| 203 |
@router.get("/api/wallet/check_order/{order_id}")
|
| 204 |
+
@_wallet_endpoint
|
| 205 |
+
def check_order(order_id: str, current_user: str = Depends(require_auth), db: Session = Depends(get_db)):
|
| 206 |
try:
|
| 207 |
info = read_order(db, order_id)
|
| 208 |
if info is None or info["account"] != current_user:
|
|
|
|
| 211 |
return {"status": "SUCCESS"}
|
| 212 |
if not alipay:
|
| 213 |
return {"status": "PENDING"}
|
| 214 |
+
result = alipay.api_alipay_trade_query(out_trade_no=order_id)
|
| 215 |
if not isinstance(result, dict) or result.get("code") != "10000":
|
| 216 |
return {"status": "PENDING"}
|
| 217 |
if result.get("out_trade_no") != order_id:
|