因為以早有實做過 OAuth2.0 毋過彼當陣前端是用別人規畫好的筐仔,所以責任無踮家己身上,但是這馬愛考慮著前端的實做,所以咧寫程式愛細膩。
嘛因為家己無啥 Vue, Nuxt 的經驗,所以家己是交予 A.I. 處理 middleware 兼 composable 的初始化。
然後就得著這兩个物件
// composables/useAuthState.ts
import { computed, ref } from 'vue'
type AuthState = 'unknown' | 'auth' | 'unauth'
interface SessionPayload {
  result: string            // "0000" means OK (adjust to your API)
  roles?: string[]
  user?: { id: string; name?: string }
}
interface Snapshot {
  state: AuthState
  roles: string[]
  user?: { id: string; name?: string }
  updatedAt: number | null
}
const HARD_TTL_MS = 15_000   // ≤ hard TTL → trust cache, no request
const SOFT_TTL_MS = 120_000  // ≤ soft TTL → allow now, refresh in background
const snap = ref<Snapshot>({ state: 'unknown', roles: [], user: undefined, updatedAt: null })
let inflight: Promise<void> | null = null
function ageMs(): number {
  return snap.value.updatedAt ? Date.now() - snap.value.updatedAt : Number.POSITIVE_INFINITY
}
function setSnapshot(next: Partial<Snapshot>) {
  snap.value = { ...snap.value, ...next, updatedAt: next.updatedAt ?? Date.now() }
  // Optional non-sensitive hint for hard reloads
  try {
    sessionStorage.setItem('auth_hint', JSON.stringify({
      state: snap.value.state, roles: snap.value.roles, updatedAt: snap.value.updatedAt
    }))
  } catch { /* ignore */ }
}
// Bootstrap from hint (optional)
try {
  if (snap.value.state === 'unknown') {
    const hint = sessionStorage.getItem('auth_hint')
    if (hint) {
      const h = JSON.parse(hint) as Partial<Snapshot>
      setSnapshot({ state: (h.state as AuthState) ?? 'unknown', roles: h.roles ?? [], updatedAt: h.updatedAt ?? null })
    }
  }
} catch { /* ignore */ }
export function useAuthState() {
  const isAuthenticated = computed(() => snap.value.state === 'auth')
  const roles = computed(() => snap.value.roles)
  const snapshot = snap
  function ttlStatus() {
    const a = ageMs()
    if (a <= HARD_TTL_MS) return 'hard-fresh' as const
    if (a <= SOFT_TTL_MS) return 'soft-fresh' as const
    return 'stale' as const
  }
  async function doFetch(): Promise<void> {
    const { $api } = useNuxtApp()
    const res = await $api<SessionPayload>('/auth/state', { credentials: 'include' })
    if (res?.result === '0000') {
      setSnapshot({ state: 'auth', roles: Array.isArray(res.roles) ? res.roles : [], user: res.user })
    } else {
      setSnapshot({ state: 'unauth', roles: [], user: undefined })
    }
  }
  async function refresh(mode: 'foreground' | 'background' = 'foreground', force = false) {
    const freshness = ttlStatus()
    // Decide necessity based on TTL and mode
    if (!force) {
      if (mode === 'foreground' && freshness !== 'stale') return
      if (mode === 'background' && (freshness === 'hard-fresh' || inflight)) return
    }
    if (inflight) return inflight // single-flight
    inflight = doFetch()
      .catch((e) => {
        // Network/other errors: keep last known state but mark time to avoid spin
        setSnapshot({ updatedAt: Date.now() })
        if (mode === 'foreground') throw e
      })
      .finally(() => { inflight = null })
    return inflight
  }
  function invalidate() {
    // Force next check to be foreground (treat as stale)
    snap.value.updatedAt = null
  }
  function backgroundRefresh() {
    return refresh('background')
  }
  return { isAuthenticated, roles, snapshot, ttlStatus, refresh, backgroundRefresh, invalidate }
}
// middleware/auth.global.ts
export default defineNuxtRouteMiddleware(async (to) => {
  // Public routes (avoid loops)
  const publicPaths = new Set(['/login', '/oauth/callback', '/help'])
  if (to.meta.public === true || publicPaths.has(to.path)) return
  const { isAuthenticated, roles, ttlStatus, refresh, backgroundRefresh } = useAuthState()
  // TTL policy: minimize blocking calls
  const freshness = ttlStatus()
  if (freshness === 'hard-fresh') {
    // trust cache
  } else if (freshness === 'soft-fresh') {
    // allow now; quietly revalidate
    void backgroundRefresh()
  } else {
    // stale/unknown → one foreground revalidation
    try { await refresh('foreground') } catch { /* last-known state will drive decision */ }
  }
  // Auth decision
  if (!isAuthenticated.value) {
    const redirect = encodeURIComponent(to.fullPath)
    return navigateTo(`/login?redirect=${redirect}`, { replace: true })
  }
  // Optional: role gating via route meta
  const need = (to.meta.roles as string[] | undefined) ?? []
  if (need.length && !need.some(r => roles.value.includes(r))) {
    return navigateTo('/forbidden', { replace: true })
  }
})
小等一下……
我只是想欲接後端 session ,轉來判斷前端敢有權限入去特定頁面。
結果哪遮複雜……
害我開始慒心是毋是家己傷菜,致使減考慮傷濟物件……
斟酌看過了後,A.I. 開誠濟段落咧處理「時間問題」,毋過可能畢竟毋是家己設計的,伊就是足「A.I.」化的段落。
然後欠缺「工程化」的結構,這實在予我足齷齪!
我感覺最近實在食緊挵破碗,緊炊無好粿,過去一直掛意的 software engineering, design pattern, system design, 最近一直攏無做到,就因為家己感覺咧走 SCRUM 結果一直感覺無需要詳細規畫,這馬顛倒一直脫箠,毋是用著錯誤的套件,就是任務、結構分割袂開,透濫結規毬,完全毋知咧創啥。
這禮拜閣是繼續燃燒性命,毋過嘛有機會靜心思考,應該會先冷靜處理一下仔未來一禮拜的規畫。