refactor(training): 用户识别支持手机号身份并规范同步旁路注释
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
+46
-36
@@ -24,7 +24,6 @@ from contextlib import asynccontextmanager
|
||||
from ..infrastructure.db import AsyncSessionLocal
|
||||
from ..infrastructure.repositories import Database
|
||||
from ..services.task_service import TaskService
|
||||
from ..api.routers.auth import _ensure_opc_identity
|
||||
from ..jwt import decode_access_token
|
||||
from ..services import sms as platform_sms
|
||||
|
||||
@@ -154,7 +153,11 @@ def require_auth(authorization: str):
|
||||
|
||||
|
||||
def _current_user(payload: dict) -> dict | None:
|
||||
"""按平台 JWT( sub/username )取平台 users 行(统一账号源)。"""
|
||||
"""按平台 JWT( sub/username )取平台 users 行(统一账号源)。
|
||||
|
||||
⚠️ 同步旁路(直连 sqlite),仅限确认与平台同库的场景;业务端点一律用
|
||||
`_current_user_async`(平台 Database,与 /auth 同源 DATABASE_URL)。
|
||||
"""
|
||||
u = None
|
||||
if payload.get("sub"):
|
||||
u = db.fetch_one("users", id=payload["sub"])
|
||||
@@ -163,6 +166,25 @@ def _current_user(payload: dict) -> dict | None:
|
||||
return u
|
||||
|
||||
|
||||
async def _current_user_async(payload: dict) -> dict | None:
|
||||
"""按平台 JWT( sub/username )取平台 users 行——走平台 Database(与 /auth 同源)。
|
||||
|
||||
旧实现直连 sqlite `serverdata/data/app.db`,与平台 `DATABASE_URL`(可配
|
||||
MySQL)不同源,平台侧创建/合并的账号(如运营端绑定账号)在此查不到 →
|
||||
报名/接单等全部 401「登录状态异常」。统一改走平台仓储。
|
||||
"""
|
||||
pdb = Database()
|
||||
try:
|
||||
u = None
|
||||
if payload.get("sub"):
|
||||
u = await pdb.users.get_by_id(payload["sub"])
|
||||
if u is None and payload.get("username"):
|
||||
u = await pdb.users.get_by_username(payload["username"])
|
||||
return u
|
||||
finally:
|
||||
await pdb.close()
|
||||
|
||||
|
||||
# ================= 工具 =================
|
||||
def read_answers(body: dict, key="answers"):
|
||||
a = body.get(key)
|
||||
@@ -258,26 +280,17 @@ async def tasks_claim_by_code(request: Request):
|
||||
async with AsyncSessionLocal() as session:
|
||||
pdb = Database(session=session)
|
||||
payload = require_auth(auth)
|
||||
username = payload.get("username") or payload.get("sub") or ""
|
||||
acct = await asyncio.to_thread(db.fetch_one, "accounts", username=username)
|
||||
if not acct:
|
||||
# 统一账号:直接按平台 JWT 定位 users 行(accounts 表已废弃清空,勿再查)。
|
||||
user = await _current_user_async(payload)
|
||||
if not user:
|
||||
raise HTTPException(status_code=401, detail="请先登录")
|
||||
key = (acct.get("phone") or "").strip() or username
|
||||
user = await pdb.users.get_by_username(key)
|
||||
if user is None:
|
||||
user = await pdb.users.create(
|
||||
key, password=secrets.token_hex(16),
|
||||
phone=(acct.get("phone") or "").strip() or "",
|
||||
nickname=(acct.get("name") or "").strip() or "",
|
||||
role="opc_member", source="mini_program", auth_type="phone",
|
||||
)
|
||||
identity = await _ensure_opc_identity(pdb, user["id"])
|
||||
# 叠加制身份:OPC 为所有账号基础权限,任何角色均可领单(role=user.role)。
|
||||
actor = dict(user)
|
||||
actor["id"] = user["id"]
|
||||
actor["username"] = user["username"]
|
||||
actor["nickname"] = user.get("nickname") or user.get("username")
|
||||
actor["port"] = "opc"
|
||||
actor["role"] = identity.get("role") or "opc_member"
|
||||
actor["role"] = user.get("role") or "opc_member"
|
||||
task = await TaskService(pdb).claim(code, actor, source="scan")
|
||||
return {"ok": True, "task": task}
|
||||
|
||||
@@ -289,14 +302,10 @@ async def tasks_my_tasks(request: Request):
|
||||
async with AsyncSessionLocal() as session:
|
||||
pdb = Database(session=session)
|
||||
payload = require_auth(auth)
|
||||
username = payload.get("username") or payload.get("sub") or ""
|
||||
acct = await asyncio.to_thread(db.fetch_one, "accounts", username=username)
|
||||
if not acct:
|
||||
# 统一账号:按平台 JWT 定位 users 行(accounts 表已废弃清空,勿再查)。
|
||||
user = await _current_user_async(payload)
|
||||
if not user:
|
||||
raise HTTPException(status_code=401, detail="请先登录")
|
||||
key = (acct.get("phone") or "").strip() or username
|
||||
user = await pdb.users.get_by_username(key)
|
||||
if user is None:
|
||||
return {"items": []}
|
||||
items = await pdb.tasks.list(status="claimed") + await pdb.tasks.list(status="doing")
|
||||
mine = [t for t in items if t.get("claimed_by") == user["id"]]
|
||||
return {"items": mine}
|
||||
@@ -404,7 +413,7 @@ async def wx_login():
|
||||
@app.get("/api/auth/me")
|
||||
async def me(authorization: str = Header(default="")):
|
||||
payload = require_auth(authorization)
|
||||
u = await asyncio.to_thread(_current_user, payload)
|
||||
u = await _current_user_async(payload)
|
||||
if not u:
|
||||
raise HTTPException(404, "账号不存在")
|
||||
return {"ok": True, "user": _user_payload(u)}
|
||||
@@ -415,7 +424,7 @@ async def update_profile(req: Request, authorization: str = Header(default="")):
|
||||
"""更新当前用户资料:昵称/头像 + 报名资料(status/topics/source)"""
|
||||
payload = require_auth(authorization)
|
||||
b = await req.json()
|
||||
u = await asyncio.to_thread(_current_user, payload)
|
||||
u = await _current_user_async(payload)
|
||||
if not u:
|
||||
raise HTTPException(404, "账号不存在")
|
||||
patch = {}
|
||||
@@ -432,7 +441,7 @@ async def update_profile(req: Request, authorization: str = Header(default="")):
|
||||
patch["topics"] = json.dumps(b["topics"], ensure_ascii=False) if isinstance(b["topics"], list) else str(b["topics"])
|
||||
if patch:
|
||||
await asyncio.to_thread(db.update_row, "users", u["id"], patch)
|
||||
u = await asyncio.to_thread(_current_user, payload)
|
||||
u = await _current_user_async(payload)
|
||||
return {"ok": True, "user": _user_payload(u)}
|
||||
|
||||
|
||||
@@ -458,7 +467,7 @@ async def wx_phone(req: Request, authorization: str = Header(default="")):
|
||||
phone = (data.get("phone_info") or {}).get("purePhoneNumber", "")
|
||||
if not phone:
|
||||
raise HTTPException(400, "未获取到手机号")
|
||||
u = await asyncio.to_thread(_current_user, payload)
|
||||
u = await _current_user_async(payload)
|
||||
if not u:
|
||||
raise HTTPException(404, "账号不存在")
|
||||
await asyncio.to_thread(_set_user_phone, u["username"], phone)
|
||||
@@ -475,7 +484,7 @@ async def bind_phone(req: Request, authorization: str = Header(default="")):
|
||||
raise HTTPException(400, "请输入正确的 11 位手机号")
|
||||
if not platform_sms.verify(phone, code):
|
||||
raise HTTPException(401, "验证码错误或已过期")
|
||||
u = await asyncio.to_thread(_current_user, payload)
|
||||
u = await _current_user_async(payload)
|
||||
if not u:
|
||||
raise HTTPException(404, "账号不存在")
|
||||
await asyncio.to_thread(_set_user_phone, u["username"], phone)
|
||||
@@ -636,14 +645,15 @@ async def create_booking(req: Request, authorization: str = Header(default="")):
|
||||
|
||||
# 强制登录:必须携带有效 token(无登录 → 401)
|
||||
payload = require_auth(authorization)
|
||||
acct = await asyncio.to_thread(_current_user, payload)
|
||||
acct = await _current_user_async(payload)
|
||||
if not acct:
|
||||
raise HTTPException(401, "登录状态异常,请重新登录")
|
||||
# 报名必须已绑定手机号(登录后自动/引导绑定),保证报名有联系方式
|
||||
if not acct.get("phone"):
|
||||
raise HTTPException(400, "请先绑定手机号后再报名")
|
||||
|
||||
username = payload["username"]
|
||||
# 关联键取平台 users 行的 username(JWT 断言可能缺失,且保证与 /api/bookings/mine 等一致)
|
||||
username = acct.get("username") or payload.get("username") or ""
|
||||
# 报名资料(姓名/状态/主题/来源)取账号中已保存的个人中心设置,报名区无需再填;question 按场次从请求体取
|
||||
name = (acct.get("nickname") or "") or (str(b.get("name", "")).strip() or username)
|
||||
status_label = (acct.get("opc_status") or "") or str(b.get("status", "") or "").strip()
|
||||
@@ -678,7 +688,7 @@ def _decode_bk(row):
|
||||
@app.get("/api/bookings/mine")
|
||||
async def my_bookings(authorization: str = Header(default="")):
|
||||
payload = require_auth(authorization)
|
||||
acct = await asyncio.to_thread(_current_user, payload)
|
||||
acct = await _current_user_async(payload)
|
||||
ids = {payload["username"]}
|
||||
if acct and acct.get("phone"):
|
||||
ids.add(acct["phone"])
|
||||
@@ -716,7 +726,7 @@ async def my_bookings(authorization: str = Header(default="")):
|
||||
async def my_booking_for_event(event_id: str, authorization: str = Header(default="")):
|
||||
"""查当前登录用户是否已报名该活动及其审核状态(详情页判断用)"""
|
||||
payload = require_auth(authorization)
|
||||
acct = await asyncio.to_thread(_current_user, payload)
|
||||
acct = await _current_user_async(payload)
|
||||
ids = {payload["username"]}
|
||||
if acct and acct.get("phone"):
|
||||
ids.add(acct["phone"])
|
||||
@@ -738,7 +748,7 @@ async def my_booking_for_event(event_id: str, authorization: str = Header(defaul
|
||||
async def checkin(req: Request, authorization: str = Header(default="")):
|
||||
payload = require_auth(authorization)
|
||||
b = await req.json()
|
||||
acct = await asyncio.to_thread(_current_user, payload)
|
||||
acct = await _current_user_async(payload)
|
||||
ids = {payload["username"]}
|
||||
if acct and acct.get("phone"):
|
||||
ids.add(acct["phone"])
|
||||
@@ -1045,7 +1055,7 @@ async def park_admission_submit(request: Request, authorization: str = Header(de
|
||||
if docs:
|
||||
from ..infrastructure.oss import to_object_path
|
||||
docs = {k: to_object_path(v) for k, v in docs.items()}
|
||||
u = _current_user(auth)
|
||||
u = await _current_user_async(auth)
|
||||
now = now_iso()
|
||||
rec = {
|
||||
"id": f"adm_{secrets.token_hex(8)}",
|
||||
@@ -1082,7 +1092,7 @@ async def park_admission_mine(authorization: str = Header(default="")):
|
||||
async def park_admission_list(authorization: str = Header(default="")):
|
||||
"""运营方:全部入驻申请(审核列表)。"""
|
||||
auth = require_auth(authorization)
|
||||
u = _current_user(auth)
|
||||
u = await _current_user_async(auth)
|
||||
if not u or u.get("role") != "operator":
|
||||
raise HTTPException(403, "仅运营方可查看")
|
||||
items = [_admission_view(i) for i in db.list_all("park_admissions")]
|
||||
@@ -1094,7 +1104,7 @@ async def park_admission_list(authorization: str = Header(default="")):
|
||||
async def park_admission_review(aid: str, request: Request, authorization: str = Header(default="")):
|
||||
"""运营方:审核入驻申请(approved/rejected/reviewing + 意见)。"""
|
||||
auth = require_auth(authorization)
|
||||
u = _current_user(auth)
|
||||
u = await _current_user_async(auth)
|
||||
if not u or u.get("role") != "operator":
|
||||
raise HTTPException(403, "仅运营方可审核")
|
||||
target = db.fetch_by_id("park_admissions", aid)
|
||||
|
||||
Reference in New Issue
Block a user