diff --git a/backend/src/main/java/com/designhub/config/WebConfig.java b/backend/src/main/java/com/designhub/config/WebConfig.java index a1deaf2..dce72c0 100644 --- a/backend/src/main/java/com/designhub/config/WebConfig.java +++ b/backend/src/main/java/com/designhub/config/WebConfig.java @@ -28,6 +28,7 @@ public class WebConfig implements WebMvcConfigurer { "/api/system-config/key/site.name", // 排除公开的网站设置API(按key获取) "/api/system-config/key/site.logo", // 排除公开的网站设置API(按key获取) "/api/system-config/map/group/site", // 排除公开的网站设置API(Map格式) + "/api/dingtalk/config/public", // 排除钉钉公开配置API(免登需要) "/doc.html", // 排除Swagger文档 "/swagger-ui/**", "/v3/api-docs/**", diff --git a/backend/src/main/java/com/designhub/controller/DingTalkController.java b/backend/src/main/java/com/designhub/controller/DingTalkController.java index fb21da1..0001922 100644 --- a/backend/src/main/java/com/designhub/controller/DingTalkController.java +++ b/backend/src/main/java/com/designhub/controller/DingTalkController.java @@ -32,6 +32,20 @@ public class DingTalkController { private final ObjectMapper objectMapper; private final UserMapper userMapper; + @Operation(summary = "获取钉钉公开配置(用于免登)") + @GetMapping("/config/public") + public Result> getPublicConfig() { + DingTalkConfig config = dingTalkService.getConfig(); + + Map publicConfig = new HashMap<>(); + if (config != null && config.getEnabled() == 1) { + publicConfig.put("corpId", config.getCorpId()); + publicConfig.put("agentId", config.getAgentId()); + } + + return Result.success(publicConfig); + } + @Operation(summary = "获取钉钉配置") @GetMapping("/config") public Result getConfig() { diff --git a/frontend/src/stores/auth.ts b/frontend/src/stores/auth.ts index 78baec2..b7d6dc0 100644 --- a/frontend/src/stores/auth.ts +++ b/frontend/src/stores/auth.ts @@ -152,6 +152,52 @@ export const useAuthStore = defineStore('auth', () => { } } + /** + * 钉钉免登录(使用授权码) + */ + const loginWithDingTalk = async (authCode: string) => { + try { + const response = await authApi.login({ + authCode, + loginType: 'dingtalk' + }) + + const loginData = response.data + + // 保存 token + const accessToken = loginData.token || loginData.accessToken + token.value = accessToken + localStorage.setItem('token', accessToken) + + // 登录成功后获取完整用户信息 + try { + await fetchUserInfo() + } catch (error) { + console.error('获取用户信息失败:', error) + // 使用基本信息 + const userInfoData = { + id: loginData.userId, + username: loginData.username, + realName: loginData.realName, + avatar: loginData.avatar, + email: '', + phone: '', + deptId: null, + deptName: '', + roles: loginData.roles || [], + permissions: [] + } + userInfo.value = userInfoData + localStorage.setItem('userInfo', JSON.stringify(userInfoData)) + } + + return { success: true } + } catch (error: any) { + console.error('钉钉免登录失败:', error) + return { success: false, message: error.message || '钉钉免登录失败' } + } + } + return { token, userInfo, @@ -159,6 +205,7 @@ export const useAuthStore = defineStore('auth', () => { userName, userRoles, login, + loginWithDingTalk, logout, fetchUserInfo, refreshToken diff --git a/frontend/src/views/Login.vue b/frontend/src/views/Login.vue index 9bd51f3..fbadac2 100644 --- a/frontend/src/views/Login.vue +++ b/frontend/src/views/Login.vue @@ -258,6 +258,13 @@ const registerForm = reactive({ // 钉钉登录 const dingTalkLoginEnabled = ref(false) const dingTalkLoginLoading = ref(false) +const dingTalkAutoLoginInProgress = ref(false) + +// 检测是否在钉钉环境内 +const isDingTalkEnv = () => { + const ua = navigator.userAgent.toLowerCase() + return ua.includes('dingtalk') +} // 部门列表 const departments = ref([]) @@ -411,7 +418,7 @@ const checkRegisterEnabled = async () => { } } -// 处理钉钉登录 +// 处理钉钉登录(OAuth方式,用于非钉钉环境) const handleDingTalkLogin = async () => { dingTalkLoginLoading.value = true try { @@ -438,7 +445,98 @@ const handleDingTalkLogin = async () => { } } -onMounted(() => { +// 钉钉应用内免登录 +const handleDingTalkAutoLogin = async () => { + if (dingTalkAutoLoginInProgress.value) return + dingTalkAutoLoginInProgress.value = true + + console.log('检测到钉钉环境,尝试免登录...') + + try { + // 1. 先获取钉钉配置(corpId) + const configResponse = await request.get>('/dingtalk/config/public') + if (configResponse.code !== 200 || !configResponse.data?.corpId) { + console.log('钉钉配置未完成,跳过免登录') + return + } + + const corpId = configResponse.data.corpId + console.log('获取到corpId:', corpId) + + // 2. 动态加载钉钉JSAPI + if (!(window as any).dd) { + await loadDingTalkJSAPI() + } + + const dd = (window as any).dd + if (!dd) { + console.error('钉钉JSAPI加载失败') + return + } + + // 3. 使用钉钉JSAPI获取免登授权码 + dd.ready(() => { + dd.runtime.permission.requestAuthCode({ + corpId: corpId, + onSuccess: async (result: { code: string }) => { + console.log('获取到免登授权码:', result.code) + try { + // 4. 使用授权码登录 + const loginResult = await authStore.loginWithDingTalk(result.code) + if (loginResult.success) { + message.success('钉钉免登录成功') + router.push('/') + } else { + console.log('钉钉免登录失败:', loginResult.message) + // 免登失败不显示错误,让用户手动登录 + } + } catch (error) { + console.error('钉钉免登录请求失败:', error) + } finally { + dingTalkAutoLoginInProgress.value = false + } + }, + onFail: (err: any) => { + console.log('获取免登授权码失败:', err) + dingTalkAutoLoginInProgress.value = false + } + }) + }) + + dd.error((err: any) => { + console.error('钉钉JSAPI错误:', err) + dingTalkAutoLoginInProgress.value = false + }) + + } catch (error) { + console.error('钉钉免登录异常:', error) + dingTalkAutoLoginInProgress.value = false + } +} + +// 动态加载钉钉JSAPI +const loadDingTalkJSAPI = (): Promise => { + return new Promise((resolve, reject) => { + if ((window as any).dd) { + resolve() + return + } + + const script = document.createElement('script') + script.src = 'https://g.alicdn.com/dingding/dingtalk-jsapi/3.0.12/dingtalk.open.js' + script.onload = () => { + console.log('钉钉JSAPI加载成功') + resolve() + } + script.onerror = () => { + console.error('钉钉JSAPI加载失败') + reject(new Error('Failed to load DingTalk JSAPI')) + } + document.head.appendChild(script) + }) +} + +onMounted(async () => { // 加载网站设置 siteSettingsStore.loadSiteSettings() @@ -446,11 +544,17 @@ onMounted(() => { loadDepartments() // 检查钉钉登录是否启用 - checkDingTalkLogin() + await checkDingTalkLogin() // 检查注册功能是否启用 checkRegisterEnabled() + // 如果在钉钉环境内且启用了钉钉登录,尝试自动免登 + if (isDingTalkEnv() && dingTalkLoginEnabled.value) { + console.log('检测到钉钉环境,启动免登录流程') + handleDingTalkAutoLogin() + } + // 监听网站设置更新事件(在加载后监听,确保能接收到事件) window.addEventListener('siteSettingsUpdated', handleSiteSettingsUpdate)