9d60e0afc6
统一用户账号体系(阶段1,非破坏): - UserRepository 新增 get_by_wx_mini_openid。 - /auth/wx-login:小程序登录(X-Client=miniprogram)按 wx_mini_openid 建/复用 users 账号(role=opc_member, source=mini_program, auth_type=wx_openid), 并带 nickName/avatarUrl;非小程序仍走 wx_openid。 - /auth/me + 登录响应补齐小程序契约字段 name/phone/phoneBound。 - users 增 opc_status/topics 列(alembic 0010);desktop/web profile 也输出 name/phone/phoneBound/status/topics,且 update-profile 支持 name/status/topics 映射。 - scripts/db/seed.py 增 accounts→users 幂等迁移:旧小程序账号并入全局 users (wx_mini_openid/phone 关联 + opc_member 身份),由用户执行 migrate+seed。 Co-Authored-By: Claude <noreply@anthropic.com>
144 lines
4.5 KiB
Python
144 lines
4.5 KiB
Python
from pydantic import BaseModel, Field
|
||
|
||
class IdentityInfo(BaseModel):
|
||
"""账号绑定的一端口身份摘要(登录/选择身份时返回)。"""
|
||
|
||
id: str
|
||
port: str = "opc"
|
||
role: str = "opc_member"
|
||
sub_role: str | None = None
|
||
name: str = ""
|
||
org_id: str | None = None
|
||
region_id: str | None = None
|
||
org_name: str | None = None
|
||
region_name: str | None = None
|
||
status: str = "active"
|
||
|
||
class LoginRequest(BaseModel):
|
||
username: str
|
||
password: str
|
||
expires_in: int | None = Field(
|
||
default=None,
|
||
description="令牌有效期(秒),-1/0 表示永久,省略为 7 天",
|
||
)
|
||
|
||
class RegisterRequest(BaseModel):
|
||
username: str
|
||
password: str
|
||
expires_in: int | None = Field(
|
||
default=None,
|
||
description="令牌有效期(秒),-1/0 表示永久,省略为 7 天",
|
||
)
|
||
|
||
class UpdateProfileRequest(BaseModel):
|
||
"""更新资料/凭据。``current_password`` 仅在修改用户名/密码时校验。"""
|
||
|
||
current_password: str = ""
|
||
new_username: str | None = None
|
||
new_password: str | None = None
|
||
expires_in: int | None = None
|
||
# 演示用户资料字段(账户管理页可编辑)
|
||
nickname: str | None = None
|
||
# 小程序契约字段(name→nickname;status→opc_status;topics→JSON 列表)
|
||
name: str | None = None
|
||
status: str | None = None
|
||
topics: list[str] | None = None
|
||
account: str | None = None
|
||
company: str | None = None
|
||
room: str | None = None
|
||
avatar: str | None = None
|
||
company_avatar: str | None = None
|
||
|
||
class RevokeTokenRequest(BaseModel):
|
||
token: str | None = Field(
|
||
default=None,
|
||
description="要吊销的令牌;省略则吊销当前令牌",
|
||
)
|
||
|
||
class AuthStatusResponse(BaseModel):
|
||
enabled: bool
|
||
has_users: bool
|
||
login_modes: list[str] = ["password", "phone"] # 本期:账号密码 + 手机号验证码;微信扫码预留
|
||
wechat_login: bool = False # 微信扫码(预留,本期未开放)
|
||
|
||
class VerifyResponse(BaseModel):
|
||
valid: bool
|
||
username: str = ""
|
||
|
||
class UserProfile(BaseModel):
|
||
"""用户资料(登录/me/update-profile 均返回该结构)。"""
|
||
|
||
username: str = ""
|
||
nickname: str = ""
|
||
name: str = "" # 别名 = nickname(小程序契约:显示名 / 报名资料姓名)
|
||
phone: str = "" # 已绑定手机号
|
||
phoneBound: bool = False # 是否已绑手机(小程序「已绑定手机」徽标/绑定门槛)
|
||
account: str = ""
|
||
company: str = ""
|
||
room: str = ""
|
||
avatar: str = ""
|
||
company_avatar: str = ""
|
||
# RBAC(登录/me 返回;update-profile 不重签时不返回权限明细)
|
||
role: str = "opc_member"
|
||
sub_role: str | None = None
|
||
org_id: str | None = None
|
||
region_id: str | None = None
|
||
permissions: list[str] = Field(default_factory=list)
|
||
scope_region_ids: list[str] = Field(default_factory=list)
|
||
# 当前生效的端口身份(多身份绑定)
|
||
identity_id: str | None = None
|
||
port: str | None = None
|
||
identity_name: str = ""
|
||
|
||
class LoginResponse(UserProfile):
|
||
"""登录/注册成功后的响应:资料 + 新令牌。"""
|
||
|
||
token: str = ""
|
||
identities: list[IdentityInfo] = Field(default_factory=list)
|
||
|
||
class ProfileResponse(UserProfile):
|
||
"""update-profile 成功后的响应;修改凭据时附带新令牌。"""
|
||
|
||
token: str = ""
|
||
identities: list[IdentityInfo] = Field(default_factory=list)
|
||
|
||
class SelectIdentityRequest(BaseModel):
|
||
"""切换到指定端口身份:为新身份签发令牌。"""
|
||
|
||
identity_id: str
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 统一登录(手机验证码 / 微信)—— 六端登录归一
|
||
# ---------------------------------------------------------------------------
|
||
|
||
class SendCodeRequest(BaseModel):
|
||
phone: str = Field(description="11 位手机号(作为登录账号)")
|
||
|
||
|
||
class SendCodeResponse(BaseModel):
|
||
sent: bool = True
|
||
message: str = "验证码已发送"
|
||
# 非空时表示走 stub 直发(仅开发/联调可见)
|
||
stub_code: str | None = None
|
||
|
||
|
||
class PhoneLoginRequest(BaseModel):
|
||
phone: str
|
||
code: str = Field(description="手机号接收到的 6 位验证码")
|
||
|
||
|
||
class WxLoginRequest(BaseModel):
|
||
code: str = Field(description="wx.login 返回的 code(未配置 appid 时当作 openid 直通)")
|
||
nickName: str = ""
|
||
avatarUrl: str = ""
|
||
|
||
|
||
class WxPhoneRequest(BaseModel):
|
||
openid: str
|
||
phone: str = Field(description="待补绑的手机号")
|
||
code: str = Field(description="手机号接收到的验证码")
|
||
|
||
|
||
# RBAC 管理请求/响应
|
||
|