feat: 完善鉴权和密码功能,添加飞书通知模块

- 完善Token过期/不存在时跳转登录重新认证
- 实现正常的账号密码登录验证(邮箱/手机号+密码)
- 用户初始化密码设为工号(sn)
- 添加修改密码功能(API+前端页面)
- 修复扫码登录后跳转问题(zustand persist格式)
- 创建飞书通知服务 FeishuNotificationService
- 设置页面添加修改密码入口

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
root
2026-02-26 15:19:37 +08:00
parent ba555a59b7
commit 2e5f570b40
26 changed files with 2925 additions and 158 deletions
+107 -19
View File
@@ -12,10 +12,12 @@ declare global {
goto: string
width?: string
height?: string
style?: string
}) => {
matchOrigin: (origin: string) => boolean
matchData: (data: any) => boolean
}
LarkSSOSDKWebQRCode?: any
}
}
@@ -39,17 +41,36 @@ export default function QRCodeLogin({ visible, onClose }: QRCodeLoginProps) {
initQRCode()
}
// 监听来自回调页面的成功消息
const handleCallbackMessage = (event: MessageEvent) => {
if (event.data?.type === 'feishu-login-success') {
console.log('✅ Received login success from callback popup')
// 轮询应该会检测到成功状态,但这里作为额外的触发器
// 如果轮询已经检测到并处理了,这个消息就忽略
}
}
window.addEventListener('message', handleCallbackMessage)
return () => {
// 清理
// 清理 message 事件监听器
if (qrObjRef.current?._cleanup) {
qrObjRef.current._cleanup()
}
// 清理回调消息监听器
window.removeEventListener('message', handleCallbackMessage)
// 清理容器
if (qrContainerRef.current) {
qrContainerRef.current.innerHTML = ''
}
// 清理轮询
if (pollTimerRef.current) {
clearTimeout(pollTimerRef.current)
}
// 重置状态
setUseFallback(false)
setQrcodeUrl('')
qrObjRef.current = null
}
}, [visible])
@@ -89,25 +110,90 @@ export default function QRCodeLogin({ visible, onClose }: QRCodeLoginProps) {
// 检查飞书 QR SDK 是否已加载(在 index.html 中引入)
if (window.QRLogin) {
console.log('✅ Using Feishu QR SDK')
// 使用飞书官方 QR SDK
// 先清空容器内容(保留容器元素本身)
const container = document.getElementById('feishu-qrcode-container')
if (container) {
container.innerHTML = ''
}
// 确保 DOM 已渲染后再调用 SDK
setTimeout(() => {
qrObjRef.current = window.QRLogin({
id: 'feishu-qrcode-container',
goto: authUrl,
width: '250',
height: '250',
style: 'width:300px;height:270px;border:none'
})
}, 50)
setStatus('pending')
startPolling(result.data.session_id)
// 确保 DOM 已渲染后再调用 SDK
// Dialog 内容可能需要更长时间渲染,使用多次尝试
const initQRSDK = (retryCount = 0) => {
const container = document.getElementById('feishu-qrcode-container')
console.log('🔍 Looking for container, attempt', retryCount + 1, 'found:', !!container)
if (container) {
// 清空容器
container.innerHTML = ''
try {
console.log('🔧 Initializing QR SDK with goto:', authUrl.substring(0, 50) + '...')
qrObjRef.current = window.QRLogin({
id: 'feishu-qrcode-container',
goto: authUrl,
width: '250',
height: '250',
style: 'width:300px;height:270px;border:none'
})
console.log('✅ Feishu QR SDK initialized successfully')
console.log('📦 Container children count:', container.children.length)
setStatus('pending')
startPolling(result.data.session_id)
// 监听扫码后的 message 事件(关键步骤!)
const handleMessage = (event: MessageEvent) => {
console.log('📨 Received message event:', event.origin, event.data)
// 安全校验
if (!qrObjRef.current?.matchOrigin(event.origin)) {
console.warn('⚠️ Origin mismatch:', event.origin)
return
}
if (!qrObjRef.current?.matchData(event.data)) {
console.warn('⚠️ Data mismatch:', event.data)
return
}
// 获取临时授权码
const loginTmpCode = event.data?.tmp_code
if (loginTmpCode) {
console.log('✅ Got tmp_code:', loginTmpCode)
console.log('🔀 Redirecting to Feishu auth page...')
// 保存当前 URL,登录成功后返回
const currentUrl = window.location.href
sessionStorage.setItem('feishu_login_return_url', currentUrl)
// 重定向到飞书授权页面(官方文档推荐方式)
const authUrlWithCode = `${authUrl}&tmp_code=${loginTmpCode}`
window.location.href = authUrlWithCode
}
}
window.addEventListener('message', handleMessage)
// 保存清理函数
qrObjRef.current._cleanup = () => {
window.removeEventListener('message', handleMessage)
}
} catch (e) {
console.error('❌ QR SDK init error:', e)
// Fallback to normal QR code
setQrcodeUrl(authUrl)
setUseFallback(true)
startPolling(result.data.session_id)
}
} else if (retryCount < 10) {
// 容器还未渲染,继续等待
setTimeout(() => initQRSDK(retryCount + 1), 100)
} else {
console.error('❌ Container not found after 10 attempts, using fallback')
setQrcodeUrl(authUrl)
setUseFallback(true)
startPolling(result.data.session_id)
}
}
// 开始初始化
setTimeout(() => initQRSDK(), 100)
} else {
// SDK 未加载,使用普通二维码
console.warn('⚠️ Feishu QR SDK not loaded, using fallback')
@@ -126,6 +212,8 @@ export default function QRCodeLogin({ visible, onClose }: QRCodeLoginProps) {
}
const startPolling = (id: string) => {
console.log('🔄 Starting polling for session:', id)
const poll = async () => {
try {
const response = await fetch(`/api/v1/auth/qrcode-login/status?session_id=${id}`)