a1886074dd
后端: FastAPI + SQLAlchemy 2.0 (async) + Alembic + MinIO + Casdoor + 企微 前端: Vue 3 + Vite + TypeScript + Element Plus + Pinia 功能清单: - 8 张数据表自动建表 / Casdoor OIDC 登录 / 企微静默登录 - 双布局: 移动端(填报) + PC端(汇总管理) - 拜访记录 CRUD + MinIO 照片直传 + 缩略图预览 + 同访人草稿 - 今日纪要 (6 分类) / 工作计划 / 小微商机 / 要客拜访 CRUD - 客户档案: 备注/收支费用/联系人/归属分配/批量转移 - 客户导入导出 + 模板下载 + 搜索/分页/筛选 - 仪表盘: 四卡统计 + 填报进度 (拜访+纪要双维度) - 周报详情: 五 Tab + 按人/客户筛选 + 时间轴 - 用户管理 / 客户经理 PC 端工作台 - 企微: 催办/公告/定时提醒 / 时区修正 - Docker 部署配置 Co-Authored-By: Claude <noreply@anthropic.com>
107 lines
3.8 KiB
Python
107 lines
3.8 KiB
Python
from uuid import uuid4
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
from sqlalchemy import select
|
|
import httpx
|
|
from app.config import settings
|
|
from app.models.user import User
|
|
from app.utils.security import create_access_token
|
|
|
|
|
|
async def get_or_create_user_from_casdoor(
|
|
db: AsyncSession, casdoor_id: str, name: str,
|
|
role: str = "manager", department: str = ""
|
|
) -> User:
|
|
"""Find existing user by casdoor_id, or create a new one."""
|
|
result = await db.execute(select(User).where(User.casdoor_id == casdoor_id))
|
|
user = result.scalar_one_or_none()
|
|
if user is None:
|
|
user = User(
|
|
casdoor_id=casdoor_id,
|
|
name=name,
|
|
role=role,
|
|
department=department,
|
|
)
|
|
db.add(user)
|
|
await db.commit()
|
|
await db.refresh(user)
|
|
else:
|
|
# Update name/department if changed
|
|
if user.name != name or user.department != department:
|
|
user.name = name
|
|
user.department = department
|
|
await db.commit()
|
|
await db.refresh(user)
|
|
return user
|
|
|
|
|
|
async def exchange_casdoor_code(code: str) -> dict | None:
|
|
"""Exchange Casdoor OIDC authorization code for user info."""
|
|
import logging
|
|
logger = logging.getLogger("uvicorn")
|
|
|
|
token_url = f"{settings.CASDOOR_ENDPOINT}/api/login/oauth/access_token"
|
|
async with httpx.AsyncClient() as client:
|
|
try:
|
|
resp = await client.post(token_url, data={
|
|
"grant_type": "authorization_code",
|
|
"client_id": settings.CASDOOR_CLIENT_ID,
|
|
"client_secret": settings.CASDOOR_CLIENT_SECRET,
|
|
"code": code,
|
|
}, timeout=10)
|
|
if resp.status_code != 200:
|
|
logger.error(f"[Casdoor] token exchange failed: status={resp.status_code}, body={resp.text[:500]}")
|
|
return None
|
|
token_data = resp.json()
|
|
access_token = token_data.get("access_token", "")
|
|
if not access_token:
|
|
logger.error(f"[Casdoor] no access_token in response: {token_data}")
|
|
return None
|
|
except Exception as e:
|
|
logger.error(f"[Casdoor] token exchange exception: {e}")
|
|
return None
|
|
|
|
userinfo_url = f"{settings.CASDOOR_ENDPOINT}/api/userinfo"
|
|
async with httpx.AsyncClient() as client:
|
|
try:
|
|
resp = await client.get(userinfo_url, headers={
|
|
"Authorization": f"Bearer {access_token}"
|
|
}, timeout=10)
|
|
if resp.status_code != 200:
|
|
logger.error(f"[Casdoor] userinfo failed: status={resp.status_code}, body={resp.text[:500]}")
|
|
return None
|
|
return resp.json()
|
|
except Exception as e:
|
|
logger.error(f"[Casdoor] userinfo exception: {e}")
|
|
return None
|
|
return None
|
|
|
|
|
|
async def bind_wecom_user(db: AsyncSession, casdoor_id: str, wecom_userid: str) -> User | None:
|
|
"""Bind a WeChat Work userid to a Casdoor user."""
|
|
result = await db.execute(select(User).where(User.casdoor_id == casdoor_id))
|
|
user = result.scalar_one_or_none()
|
|
if user is None:
|
|
return None
|
|
user.wecom_userid = wecom_userid
|
|
await db.commit()
|
|
await db.refresh(user)
|
|
return user
|
|
|
|
|
|
async def get_user_by_wecom_id(db: AsyncSession, wecom_userid: str) -> User | None:
|
|
"""Find user by wecom_userid."""
|
|
result = await db.execute(select(User).where(User.wecom_userid == wecom_userid))
|
|
return result.scalar_one_or_none()
|
|
|
|
|
|
def build_token_for_user(user: User) -> str:
|
|
"""Build a JWT token for the given user."""
|
|
return create_access_token(data={
|
|
"sub": str(user.id),
|
|
"user_id": str(user.id),
|
|
"casdoor_id": user.casdoor_id,
|
|
"name": user.name,
|
|
"role": user.role,
|
|
"department": user.department,
|
|
})
|