This commit is contained in:
eric
2026-03-18 02:01:52 -05:00
parent 1401f0230e
commit 89f7bce75e

View File

@@ -7,6 +7,7 @@ import json
import logging
import re
import time
from datetime import datetime, timezone, timedelta
from urllib.parse import parse_qs, unquote, urlparse
import requests
@@ -18,6 +19,12 @@ from ..config import PB_URL, PB_ADMIN_EMAIL, PB_ADMIN_PASSWORD
SITE_ID = "salon"
router = APIRouter(prefix="/api/salon", tags=["salon"])
DEFAULT_PASSWORD = "12345678"
CHINA_TZ = timezone(timedelta(hours=8))
def _now_china_str() -> str:
"""当前中国公历时间,格式 YYYY-MM-DD HH:mm:ss用于 submitted_at_cn 上传"""
return datetime.now(CHINA_TZ).strftime("%Y-%m-%d %H:%M:%S")
_SALON_ADMIN_TOKEN_CACHE: tuple[str, float] | None = None
_TOKEN_CACHE_TTL = 300
@@ -625,6 +632,72 @@ async def join_volunteers():
return await asyncio.to_thread(_fetch_volunteers_sync, token)
def _get_this_week_range_china() -> tuple[str, str]:
"""中国公历本周一 00:00 至周日 23:59:59 的日期字符串"""
now = datetime.now(CHINA_TZ)
days_to_monday = now.weekday() # Mon=0, Sun=6
monday = now - timedelta(days=days_to_monday)
sunday = monday + timedelta(days=6)
start = monday.strftime("%Y-%m-%d 00:00:00")
end = sunday.strftime("%Y-%m-%d 23:59:59")
return start, end
def _get_this_week_range_iso() -> tuple[str, str]:
"""中国公历本周的 UTC 时间范围,用于 created 过滤。PocketBase 要求 Y-m-d H:i:s.uZ 格式(空格非 T"""
now = datetime.now(CHINA_TZ)
days_to_monday = now.weekday()
monday = now - timedelta(days=days_to_monday)
sunday = monday + timedelta(days=6)
monday_start = monday.replace(hour=0, minute=0, second=0, microsecond=0)
sunday_end = sunday.replace(hour=23, minute=59, second=59, microsecond=999999)
start_utc = monday_start.astimezone(timezone.utc).strftime("%Y-%m-%d %H:%M:%S.000Z")
end_utc = sunday_end.astimezone(timezone.utc).strftime("%Y-%m-%d %H:%M:%S.999Z")
return start_utc, end_utc
def _fetch_stats_sync() -> dict:
"""同步获取报名统计,按场次返回 count 和 joiners。使用 get_pb_client 与 by-wechat 一致"""
try:
from ..services import get_pb_client
pb_client = get_pb_client()
solan = pb_client.collection("solanRed")
except Exception as e:
logging.warning(f"salon join stats 初始化失败: {e}")
return {"ok": True, "stats": {"session-1": {"count": 0, "joiners": []}, "session-2": {"count": 0, "joiners": []}}}
sessions = [
{"id": "session-1", "day_keyword": "周六"},
{"id": "session-2", "day_keyword": "周日"},
]
result = {}
_get = lambda r, k, d=None: getattr(r, k, d) if hasattr(r, k) else (r.get(k, d) if isinstance(r, dict) else d)
for s in sessions:
sid = s["id"]
day_kw = s["day_keyword"]
meetupdate_filter = f'(meetupdate ~ "{day_kw}" || meetupdate = "")' if sid == "session-1" else f'meetupdate ~ "{day_kw}"'
try:
existing = solan.get_list(1, 6, {"filter": meetupdate_filter, "sort": "-created"})
items = list(existing.items) if existing.items else []
total = existing.total_items if hasattr(existing, "total_items") else len(items)
joiners = []
for r in items:
nickname = str(_get(r, "xiaohongshu_nickname") or _get(r, "nickname") or "").strip() or "匿名"
avatar = str(_get(r, "xiaohongshu_avatar") or _get(r, "media") or "").strip() or None
xh_url = str(_get(r, "xiaohongshu_url") or "").strip() or None
joiners.append({"nickname": nickname, "avatar": avatar, "xiaohongshu_url": xh_url})
result[sid] = {"count": total, "joiners": joiners}
except Exception as e:
logging.warning(f"salon join stats 查询失败 {sid}: {e}")
result[sid] = {"count": 0, "joiners": []}
return {"ok": True, "stats": result}
@router.get("/join/stats")
async def join_stats():
"""按场次返回报名人数和最近报名者"""
return await asyncio.to_thread(_fetch_stats_sync)
class XiaohongshuProfileRequest(BaseModel):
url: str | None = None
id: str | None = None
@@ -1501,11 +1574,13 @@ async def submit_join(item: dict):
pb_client = get_pb_client()
solan = pb_client.collection("solanRed")
raw_type = item.get("type", "")
# PocketBase type 仅接受 session-1/session-2 等digital-nomad/volunteer 映射为空
pb_type = raw_type if raw_type in ("session-1", "session-2") else ""
# PocketBase type 字段为 select 枚举,可能仅接受 digital-nomad/volunteer 等session-1/session-2 会报 validation_invalid_value
# 场次信息存 meetupdatestats 按 meetupdate 过滤
pb_type = ""
qualify = bool(item.get("qualify", False)) # 女性且小红书帖子>10 时自动通过
user_info_val = (item.get("userInfo") or "").strip()
meetupdate_val = str(item.get("meetupdate", "") or "").strip()
pb_data = {
"user_id": user_id,
"nickname": item.get("nickname", ""),
@@ -1528,6 +1603,8 @@ async def submit_join(item: dict):
"age_range": item.get("age_range", ""),
"first_time": item.get("first_time", ""),
"userInfo": user_info_val,
"meetupdate": meetupdate_val,
"submitted_at_cn": _now_china_str(),
}
phone_val = item.get("phone", "")
if phone_val and str(phone_val).strip():