From c5ed596008264c5ebdf5c1bf1fc9a08b27e4ff8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=89=AB=E5=9C=B0=E5=83=A7?= <357099073@qq.com> Date: Sun, 20 Sep 2026 00:19:08 +0800 Subject: [PATCH] =?UTF-8?q?=E6=89=B9=E9=87=8F=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/public/auth-callback.html | 39 + backend/src/router/index.js | 13 +- backend/src/utils/authClient.js | 42 + backend/src/utils/request.js | 13 +- .../apps/oa/schedule/components/detail.vue | 13 +- backend/src/views/auth/callback.vue | 32 +- backend/vite.config.js | 4 +- go/controllers/auth/login.go | 20 +- go/controllers/auth/oidc.go | 73 +- go/controllers/auth/session.go | 125 ++ go/controllers/auth/third.go | 592 +++++++++ go/controllers/platform_auth_client.go | 391 ++++++ go/controllers/platform_auth_config.go | 240 ++++ go/controllers/platform_auth_idp.go | 257 ++++ go/docs/sql/create_oa_compensation.sql | 104 ++ go/pkg/jwtutil/jwt.go | 10 +- go/routers/auth/auth.go | 13 + go/routers/platform/platform.go | 20 + go/scripts/uidmigrate/migrate_uid.go | 355 ++++++ go/services/admin_user.go | 145 ++- go/services/auth/identity.go | 19 + go/services/auth/idp/provider.go | 413 +++++++ go/services/auth/login.go | 33 +- go/services/auth/logout.go | 114 ++ go/services/auth/token.go | 63 +- go/services/mcp_client.go | 8 +- go/services/oa_document.go | 16 +- go/services/permission.go | 101 +- go/services/platform_auth.go | 10 +- go/services/system_email_smtp.go | 440 +++---- go/services/system_email_store.go | 270 ++--- go/services/system_sitereminder.go | 1064 ++++++++--------- go/services/tenant_user.go | 288 ++--- go/services/wechatmp/bind.go | 8 +- go/views/auth/bind.tpl | 76 ++ go/views/auth/devices.tpl | 86 ++ go/views/auth/login.tpl | 49 + platform/src/api/authClient.js | 50 + platform/src/api/authConfig.js | 27 + platform/src/api/authIdp.js | 43 + platform/src/router/index.js | 21 + .../src/views/system/authClient/index.vue | 313 +++++ .../src/views/system/authConfig/index.vue | 256 ++++ platform/src/views/system/authIdp/index.vue | 303 +++++ platform/vite.config.js | 6 +- uniapp/vite.config.js | 3 + website/vite.config.ts | 3 +- 47 files changed, 5385 insertions(+), 1199 deletions(-) create mode 100644 backend/public/auth-callback.html create mode 100644 go/controllers/auth/session.go create mode 100644 go/controllers/auth/third.go create mode 100644 go/controllers/platform_auth_client.go create mode 100644 go/controllers/platform_auth_config.go create mode 100644 go/controllers/platform_auth_idp.go create mode 100644 go/docs/sql/create_oa_compensation.sql create mode 100644 go/scripts/uidmigrate/migrate_uid.go create mode 100644 go/services/auth/idp/provider.go create mode 100644 go/services/auth/logout.go create mode 100644 go/views/auth/bind.tpl create mode 100644 go/views/auth/devices.tpl create mode 100644 platform/src/api/authClient.js create mode 100644 platform/src/api/authConfig.js create mode 100644 platform/src/api/authIdp.js create mode 100644 platform/src/views/system/authClient/index.vue create mode 100644 platform/src/views/system/authConfig/index.vue create mode 100644 platform/src/views/system/authIdp/index.vue diff --git a/backend/public/auth-callback.html b/backend/public/auth-callback.html new file mode 100644 index 0000000..ed2e046 --- /dev/null +++ b/backend/public/auth-callback.html @@ -0,0 +1,39 @@ + + + + + + 正在完成登录… + + + +
正在完成登录,请稍候…
+ + + diff --git a/backend/src/router/index.js b/backend/src/router/index.js index d0706b8..ad346af 100644 --- a/backend/src/router/index.js +++ b/backend/src/router/index.js @@ -1,6 +1,6 @@ import { createRouter, createWebHashHistory } from "vue-router"; import { convertMenusToRoutes } from "./dynamicRoutes"; -import { isSSOEnabled, redirectToAuthorize } from "@/utils/authClient"; +import { isSSOEnabled, redirectToAuthorize, ensureUserInfo } from "@/utils/authClient"; // 静态子路由:需要在 Main 框架内显示的页面 const staticMainChildren = [ @@ -335,6 +335,17 @@ router.beforeEach(async (to, from, next) => { next({ path: "/login", query: { redirect: to.path } }); return; } + + // 统一认证模式:补全用户信息(旧会话可能只有 token 没有 userInfo, + // 或迁移后 userInfo 缺失 id,会导致菜单等接口报「用户ID不存在」)。 + // 令牌失效时 ensureUserInfo 会清空登录态并返回 null,此时重新登录。 + if (isSSOEnabled()) { + const info = await ensureUserInfo(); + if (!info) { + await redirectToAuthorize(); + return; + } + } if (!dynamicRoutesAdded) { await loadAndAddDynamicRoutes(); diff --git a/backend/src/utils/authClient.js b/backend/src/utils/authClient.js index 56370f3..67e6149 100644 --- a/backend/src/utils/authClient.js +++ b/backend/src/utils/authClient.js @@ -171,6 +171,48 @@ export async function logoutSSO() { window.location.href = `${AUTH_BASE}/logout?${params.toString()}`; } +/** + * 确保本地存在完整的用户信息(含 id / group_id)。 + * + * 场景:统一认证上线或 ID 全量迁移后,浏览器里可能残留旧会话—— + * 只有 token 没有 userInfo,或 userInfo 缺少 id,菜单等接口会报 + *「用户ID不存在」。这里主动补全;若令牌已失效则清空登录态返回 null, + * 由调用方跳转登录。 + */ +export async function ensureUserInfo() { + let info = null; + try { + const raw = localStorage.getItem('userInfo'); + info = raw ? JSON.parse(raw) : null; + } catch (e) { + info = null; + } + if (info && info.id) { + return info; + } + + try { + const data = await fetchUserInfo(); + const normalized = { + id: data.id || data.sub || '', + account: data.account || data.mobile || '', + name: data.name || data.nickname || '', + group_id: data.group_id || '', + tid: data.tid || '', + tenant_name: (data.tenants || []).map((t) => t.tenant_name).join('、'), + avatar: data.avatar || '', + type: 'backend' + }; + localStorage.setItem('userInfo', JSON.stringify(normalized)); + return normalized; + } catch (e) { + // 令牌无效(例如迁移前的旧令牌):清掉本地状态,让调用方重新登录 + clearTokens(); + localStorage.removeItem('userInfo'); + return null; + } +} + /** 拉取当前登录用户(认证中心视角) */ export async function fetchUserInfo() { const res = await fetch(`${AUTH_BASE}/userinfo`, { diff --git a/backend/src/utils/request.js b/backend/src/utils/request.js index 985a2ed..ce037b7 100644 --- a/backend/src/utils/request.js +++ b/backend/src/utils/request.js @@ -1,5 +1,8 @@ import axios from 'axios'; -import { isSSOEnabled, refreshAccessToken, clearTokens } from '@/utils/authClient'; +import { isSSOEnabled, refreshAccessToken, clearTokens, redirectToAuthorize } from '@/utils/authClient'; + +// 统一认证模式下 401 后正在跳转登录的标志,避免并发请求同时触发跳转造成死循环 +let ssoRedirecting = false; // 获取API基础URL,添加调试信息 const apiBaseURL = import.meta.env.VITE_API_BASE_URL; @@ -56,10 +59,14 @@ service.interceptors.response.use( error.config.headers['Authorization'] = `Bearer ${newToken}`; return service.request(error.config); } catch (e) { + // 续期失败:清空登录态并跳认证中心重新登录(只跳一次,避免死循环) clearTokens(); localStorage.removeItem('userInfo'); - window.location.href = '#/login'; - return Promise.reject(new Error('token无效')); + if (!ssoRedirecting) { + ssoRedirecting = true; + redirectToAuthorize(); + } + return Promise.reject(new Error('token无效,请重新登录')); } } console.error('未授权,请重新登录'); diff --git a/backend/src/views/apps/oa/schedule/components/detail.vue b/backend/src/views/apps/oa/schedule/components/detail.vue index 95bae38..30bff3d 100644 --- a/backend/src/views/apps/oa/schedule/components/detail.vue +++ b/backend/src/views/apps/oa/schedule/components/detail.vue @@ -327,6 +327,14 @@ const previewList = computed(() => imageList.value.map(img => resolveUrl(img.url)) ); +// ---------- 绑定提醒的状态回显与反馈 ---------- +// 注意:这两个 ref 必须声明在下方 watch 之前。 +// watch 带 immediate:true,会在 setup 执行到该处时立即回调 loadReminderInfo(), +// 而 loadReminderInfo 同步写入 reminderInfo / reminderLoading, +// 若此时它们尚未初始化,会抛出 "Cannot access 'x' before initialization"(暂时性死区)。 +const reminderInfo = ref(null); +const reminderLoading = ref(false); + // ---------- 备注:创建 / 修改 / 完成后都可编辑 ---------- const remarkText = ref(""); const remarkSaving = ref(false); @@ -374,10 +382,7 @@ async function saveRemark() { } } -// ---------- 绑定提醒的状态回显与反馈 ---------- -const reminderInfo = ref(null); -const reminderLoading = ref(false); - +// ---------- 提醒渠道文案映射 ---------- const channelTextMap = { SMS: "短信", EMAIL: "邮件", diff --git a/backend/src/views/auth/callback.vue b/backend/src/views/auth/callback.vue index 4bc166d..5252988 100644 --- a/backend/src/views/auth/callback.vue +++ b/backend/src/views/auth/callback.vue @@ -16,10 +16,39 @@ + + diff --git a/platform/src/views/system/authConfig/index.vue b/platform/src/views/system/authConfig/index.vue new file mode 100644 index 0000000..3917274 --- /dev/null +++ b/platform/src/views/system/authConfig/index.vue @@ -0,0 +1,256 @@ + + + + + diff --git a/platform/src/views/system/authIdp/index.vue b/platform/src/views/system/authIdp/index.vue new file mode 100644 index 0000000..10875ce --- /dev/null +++ b/platform/src/views/system/authIdp/index.vue @@ -0,0 +1,303 @@ + + + + + diff --git a/platform/vite.config.js b/platform/vite.config.js index ee05411..fabcee2 100644 --- a/platform/vite.config.js +++ b/platform/vite.config.js @@ -28,8 +28,10 @@ export default defineConfig({ }, server: { host: "127.0.0.1", - port: 4000, - // 开发时前端在 4000,接口走相对路径 /platform/*、/backend/*,转发到本地 Go(当前 httpport=9000) + port: 4400, + // 端口被占用时直接报错,不再自动漂移到其他端口(避免回跳地址与实际端口不一致) + strictPort: true, + // 开发时前端在 4400,接口走相对路径 /platform/*、/backend/*,转发到本地 Go(当前 httpport=9000) proxy: { "/platform": { target: "http://127.0.0.1:9000", diff --git a/uniapp/vite.config.js b/uniapp/vite.config.js index d5e65ae..10b7e83 100644 --- a/uniapp/vite.config.js +++ b/uniapp/vite.config.js @@ -13,6 +13,9 @@ export default defineConfig({ }, }, server: { + host: '127.0.0.1', + port: 4403, + strictPort: true, proxy: { // H5 开发:api/config.js 默认 baseURL 为 /proxy-api,转发到 Go 后端 '/proxy-api': { diff --git a/website/vite.config.ts b/website/vite.config.ts index 1deb3d5..c80e7e3 100644 --- a/website/vite.config.ts +++ b/website/vite.config.ts @@ -21,7 +21,8 @@ export default defineConfig({ server: { open: true, host: '127.0.0.1', - port: 4002, + port: 4402, + strictPort: true, hmr: true, proxy: { '/api': {