From a7155fbaf2b466ba45756c6086070421ee322d9f Mon Sep 17 00:00:00 2001 From: npmrun <1549469775@qq.com> Date: Tue, 21 Apr 2026 11:07:50 +0800 Subject: [PATCH] feat(auth): implement client session synchronization and refactor authentication handling - Added `ensureClientMeSynced` function to synchronize client session with server, improving user experience by reducing unnecessary API calls. - Updated `AppShell`, `public.vue`, and other components to utilize the new synchronization method instead of the previous refresh mechanism. - Introduced a new API endpoint for session handling, ensuring consistent user state across client and server. - Refactored `useAuthSession` to manage client session state more effectively, enhancing overall authentication flow. This update streamlines the authentication process and enhances the reliability of user session management. --- app/components/AppShell.vue | 4 ++-- app/composables/useAuthSession.ts | 27 +++++++++++++++++++++++++++ app/layouts/public.vue | 4 ++-- app/pages/@[publicSlug]/posts/[postSlug].vue | 6 +----- app/pages/me/media/index.vue | 4 ---- app/pages/me/media/orphans.vue | 7 ------- app/pages/me/posts/[id].vue | 3 +-- app/pages/me/posts/index.vue | 3 +-- packages/drizzle-pkg/db.sqlite | Bin 147456 -> 147456 bytes server/api/auth/session.get.ts | 14 ++++++++++++++ server/utils/auth-api-routes.ts | 2 ++ 11 files changed, 50 insertions(+), 24 deletions(-) create mode 100644 server/api/auth/session.get.ts diff --git a/app/components/AppShell.vue b/app/components/AppShell.vue index 11098c2..b12d5c9 100644 --- a/app/components/AppShell.vue +++ b/app/components/AppShell.vue @@ -10,14 +10,14 @@ withDefaults( ) const route = useRoute() -const { loggedIn, user, refresh, clear, initialized } = useAuthSession() +const { loggedIn, user, clear, initialized, ensureClientMeSynced } = useAuthSession() const { fetchData } = useClientApi() const { allowRegister, siteName } = useGlobalConfig() const logoutLoading = ref(false) onMounted(() => { - refresh().catch(() => {}) + ensureClientMeSynced().catch(() => {}) }) const displayName = computed(() => { diff --git a/app/composables/useAuthSession.ts b/app/composables/useAuthSession.ts index 8d51de2..9eee9ef 100644 --- a/app/composables/useAuthSession.ts +++ b/app/composables/useAuthSession.ts @@ -13,6 +13,10 @@ type MeResult = { user: AuthUser; }; +type SessionResult = { + user: AuthUser | null; +}; + export type AuthSessionState = { initialized: boolean; pending: boolean; @@ -21,6 +25,8 @@ export type AuthSessionState = { }; export const AUTH_SESSION_STATE_KEY = "auth:session"; +/** 客户端是否已做过与 Cookie 的一次强制对齐(`ensureClientMeSynced`);登出等场景需重置 */ +export const AUTH_CLIENT_ME_SYNCED_KEY = "auth:client-me-synced"; export const DEFAULT_AUTH_SESSION_STATE: AuthSessionState = { initialized: false, pending: false, @@ -39,6 +45,7 @@ export function useAuthSession() { const state = useState(AUTH_SESSION_STATE_KEY, () => ({ ...DEFAULT_AUTH_SESSION_STATE, })); + const clientMeSynced = useState(AUTH_CLIENT_ME_SYNCED_KEY, () => false); const applyUser = (user: AuthUser | null) => { state.value.user = user; @@ -47,6 +54,7 @@ export function useAuthSession() { }; const clear = () => { + clientMeSynced.value = false; applyUser(null); }; @@ -75,6 +83,24 @@ export function useAuthSession() { } }; + /** + * 全站 SPA 生命周期内至多同步一次客户端会话(`GET /api/auth/session`)。 + * 无会话 Cookie 时服务端不查库;有 Cookie 时与 `/api/auth/me` 相比少一次额外查询。 + */ + const ensureClientMeSynced = async (): Promise => { + if (import.meta.server || clientMeSynced.value) { + return; + } + try { + const payload = await request>("/api/auth/session"); + const data = unwrapApiBody(payload); + applyUser(data.user); + clientMeSynced.value = true; + } catch { + /* 网络错误等:保留 SSR 状态,下次进入带壳页面可再试 */ + } + }; + return { initialized: computed(() => state.value.initialized), loggedIn: computed(() => state.value.loggedIn), @@ -82,5 +108,6 @@ export function useAuthSession() { pending: computed(() => state.value.pending), refresh, clear, + ensureClientMeSynced, }; } diff --git a/app/layouts/public.vue b/app/layouts/public.vue index f528e77..72e6584 100644 --- a/app/layouts/public.vue +++ b/app/layouts/public.vue @@ -7,7 +7,7 @@ import { import { unwrapApiBody, type ApiResponse } from '../utils/http/factory' const route = useRoute() -const { loggedIn, user, refresh, clear, initialized } = useAuthSession() +const { loggedIn, user, clear, initialized, ensureClientMeSynced } = useAuthSession() const { fetchData } = useClientApi() const { siteName, allowRegister } = useGlobalConfig() const logoutLoading = ref(false) @@ -125,7 +125,7 @@ watch(publicLayoutMode, (m) => { }) onMounted(() => { - refresh().catch(() => {}) + ensureClientMeSynced().catch(() => {}) }) diff --git a/app/pages/@[publicSlug]/posts/[postSlug].vue b/app/pages/@[publicSlug]/posts/[postSlug].vue index 514fb47..c8fbbac 100644 --- a/app/pages/@[publicSlug]/posts/[postSlug].vue +++ b/app/pages/@[publicSlug]/posts/[postSlug].vue @@ -12,7 +12,7 @@ definePageMeta({ const route = useRoute() const publicSlug = computed(() => route.params.publicSlug as string) const postSlug = computed(() => route.params.postSlug as string) -const { user, loggedIn, refresh: refreshAuth } = useAuthSession() +const { user, loggedIn } = useAuthSession() type Post = { id: number @@ -63,10 +63,6 @@ usePageTitle(() => { return [t, `@${ps}`] }) -onMounted(() => { - void refreshAuth(true) -}) - /** 当前登录用户是否为该公开主页所有者(可编辑此文) */ const canEditPost = computed(() => { const slug = user.value?.publicSlug diff --git a/app/pages/me/media/index.vue b/app/pages/me/media/index.vue index 3ed824e..14e399d 100644 --- a/app/pages/me/media/index.vue +++ b/app/pages/me/media/index.vue @@ -1,6 +1,4 @@