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>
94 lines
3.5 KiB
Python
94 lines
3.5 KiB
Python
from fastapi import APIRouter, Depends, HTTPException
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
from app.database import get_db
|
|
from app.schemas.user import TokenResponse, WecomLoginRequest, CasdoorLoginRequest, WecomBindRequest
|
|
from app.services.auth import (
|
|
exchange_casdoor_code, get_or_create_user_from_casdoor,
|
|
get_user_by_wecom_id, bind_wecom_user, build_token_for_user,
|
|
)
|
|
from app.services.wecom import wecom_client
|
|
from app.config import settings
|
|
|
|
router = APIRouter(prefix="/auth", tags=["Authentication"])
|
|
|
|
|
|
@router.post("/casdoor-login", response_model=TokenResponse)
|
|
async def casdoor_login(req: CasdoorLoginRequest, db: AsyncSession = Depends(get_db)):
|
|
"""Standard Casdoor OIDC login — exchange code for userinfo, get or create user, return JWT."""
|
|
userinfo = await exchange_casdoor_code(req.code)
|
|
if not userinfo:
|
|
raise HTTPException(status_code=400, detail="Failed to exchange code with Casdoor")
|
|
|
|
casdoor_id = userinfo.get("sub") or userinfo.get("id")
|
|
if not casdoor_id:
|
|
raise HTTPException(status_code=400, detail="Invalid userinfo from Casdoor")
|
|
|
|
name = userinfo.get("name") or userinfo.get("preferred_username") or casdoor_id
|
|
role = userinfo.get("role", "manager")
|
|
|
|
user = await get_or_create_user_from_casdoor(db, casdoor_id, name, role)
|
|
token = build_token_for_user(user)
|
|
|
|
return TokenResponse(
|
|
access_token=token,
|
|
user_id=str(user.id),
|
|
name=user.name,
|
|
role=user.role,
|
|
)
|
|
|
|
|
|
@router.post("/wecom-login")
|
|
async def wecom_login(req: WecomLoginRequest, db: AsyncSession = Depends(get_db)):
|
|
"""WeChat Work silent login — exchange wecom code for userid, find bound user, return JWT."""
|
|
userinfo = await wecom_client.get_userinfo_by_code(req.code)
|
|
if not userinfo:
|
|
raise HTTPException(status_code=400, detail="Failed to exchange wecom code")
|
|
|
|
wecom_userid = userinfo.get("UserId") or userinfo.get("userid")
|
|
if not wecom_userid:
|
|
raise HTTPException(status_code=400, detail="Could not get userid from wecom")
|
|
|
|
user = await get_user_by_wecom_id(db, wecom_userid)
|
|
if user:
|
|
token = build_token_for_user(user)
|
|
return TokenResponse(
|
|
access_token=token,
|
|
user_id=str(user.id),
|
|
name=user.name,
|
|
role=user.role,
|
|
)
|
|
|
|
# Not bound yet — return a redirect URL to Casdoor for binding
|
|
casdoor_auth_url = (
|
|
f"{settings.CASDOOR_ENDPOINT}/login/oauth/authorize"
|
|
f"?client_id={settings.CASDOOR_CLIENT_ID}"
|
|
f"&response_type=code"
|
|
f"&redirect_uri={settings.CORS_ORIGINS[0]}/bind-wecom"
|
|
f"&scope=openid+profile"
|
|
f"&state={wecom_userid}"
|
|
)
|
|
return {"need_bind": True, "casdoor_url": casdoor_auth_url, "wecom_userid": wecom_userid}
|
|
|
|
|
|
@router.post("/bind-wecom")
|
|
async def bind_wecom(req: WecomBindRequest, db: AsyncSession = Depends(get_db)):
|
|
"""Bind Casdoor account with WeChat Work userid after OIDC redirect."""
|
|
userinfo = await exchange_casdoor_code(req.casdoor_code)
|
|
if not userinfo:
|
|
raise HTTPException(status_code=400, detail="Failed to exchange casdoor code")
|
|
|
|
casdoor_id = userinfo.get("sub") or userinfo.get("id")
|
|
wecom_userid = req.wecom_userid or userinfo.get("state", "")
|
|
|
|
user = await bind_wecom_user(db, casdoor_id, wecom_userid)
|
|
if not user:
|
|
raise HTTPException(status_code=404, detail="User not found")
|
|
|
|
token = build_token_for_user(user)
|
|
return TokenResponse(
|
|
access_token=token,
|
|
user_id=str(user.id),
|
|
name=user.name,
|
|
role=user.role,
|
|
)
|