Files
qiji/backend/app/api/users.py
T
v6ole 7264184315 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>
2026-06-26 12:10:38 +08:00

122 lines
3.7 KiB
Python

import uuid
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, Query
from pydantic import BaseModel
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
from app.database import get_db
from app.middleware.auth import get_current_user, require_director
from app.models.user import User
from app.schemas.customer import UserOut
router = APIRouter(prefix="/users", tags=["Users"])
class UpdateUserRoleRequest(BaseModel):
role: str # manager / director / leader
department: str = ""
@router.get("/", response_model=list[UserOut])
async def list_users(
role: Optional[str] = Query(None),
current_user: dict = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
"""List all users. Used for companion selection, assignment, etc."""
query = select(User)
if role:
query = query.where(User.role == role)
query = query.order_by(User.name)
result = await db.execute(query)
return result.scalars().all()
@router.get("/me")
async def get_current_user_info(
current_user: dict = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
"""Get current user's full profile."""
result = await db.execute(select(User).where(User.id == uuid.UUID(current_user["user_id"])))
user = result.scalar_one_or_none()
if not user:
raise HTTPException(status_code=404, detail="User not found")
return {
"id": str(user.id),
"casdoor_id": user.casdoor_id,
"name": user.name,
"role": user.role,
"department": user.department,
"wecom_userid": user.wecom_userid,
}
@router.put("/{user_id}/role")
async def update_user_role(
user_id: str,
data: UpdateUserRoleRequest,
current_user: dict = Depends(require_director),
db: AsyncSession = Depends(get_db),
):
"""Director updates a user's role. Only director can do this."""
if data.role not in ("manager", "director", "leader"):
raise HTTPException(status_code=400, detail="Invalid role. Must be manager/director/leader")
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")
user.role = data.role
if data.department:
user.department = data.department
await db.commit()
await db.refresh(user)
return {
"id": str(user.id),
"name": user.name,
"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,
}