7fd70a264d
- 服务号网页授权(WX_MP_APPID/SECRET,区别于 App 的 WECHAT_APP_ID):
落地页 base 静默拿 openid;点「领券」补 userinfo 拿昵称头像(交互触发避快照页)
- 新表 cps_wx_user + cps_click 加 openid + 迁移;integrations/wx_oauth
- /c/{code} 授权链路 + 回调 /wx/oauth/cb + cookie 免重复授权;click 带 openid
- admin 群详情加「群内微信用户」(领券画像)端点
- 下单归因到人需 user-level sid,本期只做身份+领券侧
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
43 lines
2.0 KiB
Python
43 lines
2.0 KiB
Python
"""CPS 落地页微信用户(cps_wx_user)。
|
|
|
|
用户在微信内打开群发短链落地页 /c/{code},经服务号网页授权:
|
|
- base 静默:拿 openid(唯一标识,统计主力)
|
|
- userinfo(点领券触发):补 nickname/headimgurl/unionid
|
|
|
|
按 openid 唯一,记录首次来源群(first_group_id),用于群内用户级统计(谁领了券)。
|
|
下单归因到人需 user-level sid(另一期),本表只承载身份 + 领券/点击侧。
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime
|
|
|
|
from sqlalchemy import DateTime, Integer, String, func
|
|
from sqlalchemy.orm import Mapped, mapped_column
|
|
|
|
from app.db.base import Base
|
|
|
|
|
|
class CpsWxUser(Base):
|
|
__tablename__ = "cps_wx_user"
|
|
|
|
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
|
# 服务号下用户唯一标识(网页授权拿到)
|
|
openid: Mapped[str] = mapped_column(String(64), unique=True, index=True, nullable=False)
|
|
# 开放平台 unionid(服务号绑开放平台 + scope=userinfo 才有);跨 App/服务号统一用户
|
|
unionid: Mapped[str | None] = mapped_column(String(64), index=True, nullable=True)
|
|
# 昵称/头像:userinfo 授权后才有(base 阶段为空)
|
|
nickname: Mapped[str | None] = mapped_column(String(128), nullable=True)
|
|
headimgurl: Mapped[str | None] = mapped_column(String(512), nullable=True)
|
|
# 首次进入来源(从哪个群的链接授权进来)
|
|
first_code: Mapped[str | None] = mapped_column(String(16), nullable=True)
|
|
first_group_id: Mapped[int | None] = mapped_column(Integer, index=True, nullable=True)
|
|
first_seen: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), server_default=func.now(), nullable=False
|
|
)
|
|
last_seen: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), server_default=func.now(), onupdate=func.now(), nullable=False
|
|
)
|
|
|
|
def __repr__(self) -> str: # pragma: no cover
|
|
return f"<CpsWxUser id={self.id} openid={self.openid!r} nickname={self.nickname!r}>"
|