%% 4.1 员工端 OAuth 静默授权(snsapi_base → ?token= 镜像) sequenceDiagram participant U as 员工(企微WebView) participant H5 as H5前端(/itdesk/) participant R as 路由守卫 participant B as 后端(/api/h5) participant W as 企微OAuth U->>H5: 打开 /itdesk/ H5->>R: beforeEach 守卫 R->>R: 读 ?token=(无) / ?code=(无) / 无 token R->>B: GET /h5/oauth/authorize (prod 校验 wxwork UA) B-->>R: {authorize_url} R->>W: 302 跳转企微授权页 W-->>H5: 回调 redirect_uri?code=CODE H5->>B: GET /h5/oauth/sns-callback?code=CODE B->>W: code 换 userid + 用户信息 B->>B: 生成 employee token 存 Redis(employee:token:) B-->>H5: 302 /itdesk/?token=XXX H5->>R: 守卫读 ?token=XXX R->>R: localStorage.h5_token = XXX;replaceState 清除 URL R->>B: 携带 Bearer 拉取用户信息 B-->>H5: 工作台数据(内层 data) %% 4.2 坐席/管理 扫码登录(auth_qrcode) sequenceDiagram participant A as 坐席/管理员 participant FE as 前端登录页 participant B as 后端(/api/auth_qrcode) participant WX as 企微App(扫码确认) participant R as Redis A->>FE: 点击「企微扫码登录」 FE->>B: POST /auth_qrcode/create B->>R: 写 ticket(120s) + OAuth URL B-->>FE: {ticket, qrcode_png_base64} FE->>FE: 展示二维码 + 2s 轮询 loop 轮询 FE->>B: GET /auth_qrcode/poll/{ticket} B-->>FE: {status: waiting/scanned} end WX->>B: GET /auth_qrcode/scan?code&state=ticket (企微OAuth回调) B->>R: 写 scan:{ticket} A->>WX: 在企微点「确认登录」 WX->>B: POST /auth_qrcode/confirm {ticket} B->>B: 校验身份→签发 token(agent/admin) B->>R: 写 confirm:{ticket}=token FE->>B: GET /poll/{ticket} → {status:confirmed, token} FE->>FE: localStorage.agent_token/admin_token = token FE->>FE: 跳 /workspace 或 / %% 4.3 坐席/管理 账号密码 + OTP sequenceDiagram participant A as 坐席/管理员 participant FE as 前端登录页 participant B as 后端(/api/agents/login) participant M as MFAService/Redis A->>FE: 输入账号+密码,点登录 FE->>B: POST /agents/login {user_id, password} alt 已绑定 MFA 且无 otp_code B-->>FE: {require_otp:true, user_id, name, role}(无 token) FE->>FE: 渲染 OTP 输入框(v-if requireOtp) A->>FE: 输入 6 位 OTP FE->>B: POST /agents/login {user_id, password, otp_code} B->>M: verify_code(mfa_secret, otp_code) M-->>B: True B->>B: 签发 token B-->>FE: {token, user_id, name, role} else 未绑定 MFA B-->>FE: {token, ...} 直接登录 end FE->>FE: localStorage.agent_token/admin_token = token;跳主页 %% 4.4 令牌过期 / 401 处理 sequenceDiagram participant FE as 三端前端 participant I as 响应拦截器 participant B as 后端 participant R as Redis FE->>B: 业务请求(Bearer token) B-->>FE: 401 / {code:1002} alt H5 员工端 I->>I: 清 h5_token alt 生产(有 CorpId) I->>B: 重走 OAuth 重定向(带防循环计数) else Mock(dev) I->>FE: 跳 /itdesk/login end else 坐席端 I->>B: POST /api/auth/refresh?token=(静默) B->>R: 延长 user:token TTL alt 刷新成功 I->>FE: 重放原请求 else 失败 I->>I: 清 agent_token(不再清 portal_token) I->>FE: 跳 /login end else 管理端 I->>I: 清 admin_token I->>FE: 跳 /login end