因為以早有實做過 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 結果一直感覺無需要詳細規畫,這馬顛倒一直脫箠,毋是用著錯誤的套件,就是任務、結構分割袂開,透濫結規毬,完全毋知咧創啥。
這禮拜閣是繼續燃燒性命,毋過嘛有機會靜心思考,應該會先冷靜處理一下仔未來一禮拜的規畫。