feat: openid 即可区分用户 — openid-login 降级建档(CRMEB 查不到也建档)+ 用户中心微信授权登录按钮

This commit is contained in:
2026-07-31 15:04:53 +08:00
parent dd4f922f95
commit 4e4df248ca
2 changed files with 45 additions and 22 deletions

View File

@@ -108,40 +108,46 @@ class OpenidRequest(BaseModel):
@router.post("/openid-login") @router.post("/openid-login")
async def openid_login(req: OpenidRequest) -> TokenResponse: async def openid_login(req: OpenidRequest) -> TokenResponse:
"""仅传 openid → 调 CRMEB 获取用户信息 → 创建/更新 User → 返回 token""" """仅传 openid → 调 CRMEB 获取用户信息(查不到也降级建档)→ 创建/更新 User → 返回 token"""
if not settings.crmeb_base_url or not settings.crmeb_api_key: if not settings.crmeb_base_url or not settings.crmeb_api_key:
raise HTTPException(status_code=500, detail="CRMEB 未配置") raise HTTPException(status_code=500, detail="CRMEB 未配置")
async with httpx.AsyncClient() as client: # 尝试从 CRMEB 拉取用户资料;失败(新用户/网络)时降级为匿名建档,openid 即身份
resp = await client.get( nickname, phone, avatar = "", "", ""
f"{settings.crmeb_base_url}/api/internal/user-info", try:
params={"openid": req.openid}, async with httpx.AsyncClient() as client:
headers={"X-Internal-Key": settings.crmeb_api_key}, resp = await client.get(
timeout=10, f"{settings.crmeb_base_url}/api/internal/user-info",
) params={"openid": req.openid},
data = resp.json() headers={"X-Internal-Key": settings.crmeb_api_key},
timeout=10,
if data.get("code") != 200: )
raise HTTPException(status_code=400, detail="获取用户信息失败") data = resp.json()
if data.get("code") == 200:
info = data["data"] info = data.get("data") or {}
nickname = (info.get("nickname") or "").strip()
phone = (info.get("phone") or "").strip()
avatar = (info.get("avatar") or "").strip()
except Exception:
pass # CRMEB 不可用时仍允许 openid 建档
# 查找或创建用户 # 查找或创建用户
user = await User.find_one(User.openid == req.openid) user = await User.find_one(User.openid == req.openid)
if not user: if not user:
user = User( user = User(
openid=req.openid, openid=req.openid,
name=info.get("nickname", "") or "", name=nickname,
phone=info.get("phone") or None, phone=phone or None,
avatar_url=info.get("avatar") or None, avatar_url=avatar or None,
) )
await user.insert() await user.insert()
else: else:
user.name = info.get("nickname", "") or user.name if nickname:
if info.get("phone"): user.name = nickname
user.phone = info["phone"] if phone:
if info.get("avatar"): user.phone = phone
user.avatar_url = info["avatar"] if avatar:
user.avatar_url = avatar
await user.save() await user.save()
token = _build_token(user) token = _build_token(user)

View File

@@ -24,6 +24,7 @@ import { Button } from "../components/ui/button";
import { Card } from "../components/ui/card"; import { Card } from "../components/ui/card";
import { Badge } from "../components/ui/badge"; import { Badge } from "../components/ui/badge";
import { useUser, getPregnancyStage } from "../context/UserContext"; import { useUser, getPregnancyStage } from "../context/UserContext";
import bridge from "../services/bridge";
interface UserInfo { interface UserInfo {
name: string; name: string;
@@ -287,6 +288,22 @@ export function ProfilePage() {
</div> </div>
</div> </div>
</Card> </Card>
{/* 微信授权登录:未绑定手机号时显示,点击跳小程序登录页完善资料 */}
{!user.phone && (
<div className="mt-4">
<button
onClick={() => bridge.requireAuth()}
className="w-full py-3 rounded-2xl bg-white border border-morandi-200 text-morandi-500 text-sm font-medium flex items-center justify-center gap-2 shadow-sm hover:bg-morandi-50"
>
<Shield className="w-4 h-4" />
微信授权登录 · 完善资料
</button>
<p className="text-center text-xs text-gray-400 mt-2">
绑定手机号后可同步头像昵称,数据不丢失
</p>
</div>
)}
</motion.div> </motion.div>
{/* 基本信息 */} {/* 基本信息 */}