feat: WeChat Pay integration, translation quota management, login UX fixes
- WeChat Pay APIv3 integration (JSAPI + Native) with cert-based auth - TranslationQuota model + admin management UI (配额 tab) - Alibaba MT provider now checks quota before translation - Fix: admin tabs scrollable on mobile, remove header-card - Fix: profile/login navigation - logout stays on profile, login returns to profile - Fix: login form now visible by default (no extra click to show) - Fix: home page translate link uses navigateTo (was switchTab to non-tabBar page) - Add .coverage and apiclient_key.pem to gitignore
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
from typing import Dict, Any, Optional
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select
|
||||
from app.models.translation_quota import TranslationQuota
|
||||
from datetime import datetime
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TranslationQuotaService:
|
||||
def __init__(self, db: AsyncSession):
|
||||
self.db = db
|
||||
|
||||
async def _get_or_create(self, version: str) -> TranslationQuota:
|
||||
result = await self.db.execute(
|
||||
select(TranslationQuota).where(TranslationQuota.version == version)
|
||||
)
|
||||
quota = result.scalar_one_or_none()
|
||||
if not quota:
|
||||
now = datetime.utcnow()
|
||||
quota = TranslationQuota(
|
||||
version=version,
|
||||
monthly_limit=1000000,
|
||||
used_chars=0,
|
||||
current_month=now.strftime("%Y-%m"),
|
||||
enabled=True,
|
||||
description=f"阿里云翻译{version}版",
|
||||
)
|
||||
self.db.add(quota)
|
||||
await self.db.flush()
|
||||
return quota
|
||||
|
||||
async def check_quota(self, version: str) -> bool:
|
||||
quota = await self._get_or_create(version)
|
||||
now = datetime.utcnow()
|
||||
current = now.strftime("%Y-%m")
|
||||
if quota.current_month != current:
|
||||
quota.current_month = current
|
||||
quota.used_chars = 0
|
||||
await self.db.flush()
|
||||
return quota.enabled and quota.used_chars < quota.monthly_limit
|
||||
|
||||
async def consume(self, version: str, chars: int):
|
||||
quota = await self._get_or_create(version)
|
||||
if not quota.enabled:
|
||||
raise ValueError(f"Translation API [{version}] is disabled")
|
||||
now = datetime.utcnow()
|
||||
current = now.strftime("%Y-%m")
|
||||
if quota.current_month != current:
|
||||
quota.current_month = current
|
||||
quota.used_chars = 0
|
||||
quota.used_chars += chars
|
||||
await self.db.flush()
|
||||
remaining = max(0, quota.monthly_limit - quota.used_chars)
|
||||
logger.info(f"Quota [{version}] consumed {chars} chars, remaining {remaining} this month")
|
||||
return remaining
|
||||
|
||||
async def get_all_quotas(self) -> list:
|
||||
default_versions = ["ecommerce", "general"]
|
||||
for v in default_versions:
|
||||
await self._get_or_create(v)
|
||||
|
||||
result = await self.db.execute(select(TranslationQuota).order_by(TranslationQuota.version))
|
||||
quotas = result.scalars().all()
|
||||
rows = []
|
||||
for q in quotas:
|
||||
now = datetime.utcnow()
|
||||
current = now.strftime("%Y-%m")
|
||||
if q.current_month != current:
|
||||
q.current_month = current
|
||||
q.used_chars = 0
|
||||
await self.db.flush()
|
||||
rows.append({
|
||||
"version": q.version,
|
||||
"monthly_limit": q.monthly_limit,
|
||||
"used_chars": q.used_chars,
|
||||
"current_month": q.current_month,
|
||||
"enabled": q.enabled,
|
||||
"description": q.description,
|
||||
})
|
||||
return rows
|
||||
|
||||
async def update_quota(self, version: str, data: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
||||
quota = await self._get_or_create(version)
|
||||
if "monthly_limit" in data:
|
||||
quota.monthly_limit = int(data["monthly_limit"])
|
||||
if "enabled" in data:
|
||||
quota.enabled = bool(data["enabled"])
|
||||
if "description" in data:
|
||||
quota.description = str(data["description"])
|
||||
await self.db.flush()
|
||||
return {
|
||||
"version": quota.version,
|
||||
"monthly_limit": quota.monthly_limit,
|
||||
"used_chars": quota.used_chars,
|
||||
"current_month": quota.current_month,
|
||||
"enabled": quota.enabled,
|
||||
"description": quota.description,
|
||||
}
|
||||
|
||||
async def reset_usage(self, version: str) -> Optional[Dict[str, Any]]:
|
||||
quota = await self._get_or_create(version)
|
||||
quota.used_chars = 0
|
||||
quota.current_month = datetime.utcnow().strftime("%Y-%m")
|
||||
await self.db.flush()
|
||||
return {
|
||||
"version": quota.version,
|
||||
"monthly_limit": quota.monthly_limit,
|
||||
"used_chars": quota.used_chars,
|
||||
"current_month": quota.current_month,
|
||||
"enabled": quota.enabled,
|
||||
}
|
||||
Reference in New Issue
Block a user