feat: 企微绑定重构 — 回调事件 + 支局长手动绑定/解绑
绑定流程改为 rural-optical-rectify 方案:
- 企微菜单「绑定账号」发送 click 事件(而非 OAuth 跳转)
- 回调 POST 接收事件 → 生成绑定 token → 推送绑定链接
- 前端 WecomBind.vue (/wecom-bind) 确认绑定
- 新增 POST /api/wecom/bind-confirm (JWT 保护)
支局长手动管理:
- 新增 PUT /api/users/{id}/wecom 绑定/解绑端点
- UserManage.vue 编辑对话框新增企微 UserID 字段
- 表格操作列新增「解绑」按钮
- 重复绑定检测
wecom.py 新增:
- in-memory bind token store (TTL 600s)
- send_text_card() 卡片消息方法
- access_token 属性暴露
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -80,3 +80,42 @@ async def update_user_role(
|
||||
"role": user.role,
|
||||
"department": user.department,
|
||||
}
|
||||
|
||||
|
||||
class UpdateWecomRequest(BaseModel):
|
||||
wecom_userid: Optional[str] = None # None or "" to unbind
|
||||
|
||||
|
||||
@router.put("/{user_id}/wecom")
|
||||
async def update_user_wecom(
|
||||
user_id: str,
|
||||
data: UpdateWecomRequest,
|
||||
current_user: dict = Depends(require_director),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""Director binds or unbinds a user's WeChat Work account."""
|
||||
result = await db.execute(select(User).where(User.id == uuid.UUID(user_id)))
|
||||
user = result.scalar_one_or_none()
|
||||
if not user:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
|
||||
new_id = (data.wecom_userid or "").strip()
|
||||
|
||||
# Check duplicate
|
||||
if new_id:
|
||||
dup = await db.execute(
|
||||
select(User).where(User.wecom_userid == new_id, User.id != uuid.UUID(user_id))
|
||||
)
|
||||
existing = dup.scalar_one_or_none()
|
||||
if existing:
|
||||
raise HTTPException(status_code=400, detail=f"该企微ID已被「{existing.name}」绑定")
|
||||
|
||||
user.wecom_userid = new_id if new_id else None
|
||||
await db.commit()
|
||||
await db.refresh(user)
|
||||
|
||||
return {
|
||||
"id": str(user.id),
|
||||
"name": user.name,
|
||||
"wecom_userid": user.wecom_userid,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user