@@ -14,11 +14,74 @@ class PermissionService {
1414 private readonly cacheExpireTime : number = 30 * 60 * 1000 // 30分钟
1515 private lastFetchTime : number = 0
1616
17+ private readonly STORAGE_KEYS = {
18+ PERMISSIONS : 'user_permissions' ,
19+ FETCH_TIME : 'permissions_fetch_time' ,
20+ AUTH_KEY : 'permissions_auth_key' ,
21+ // 用于开发/演示的本地覆盖(不应污染真实登录态)
22+ ROLE_OVERRIDE : 'user_role' ,
23+ FORCE_DEMO_SWITCH : 'force_demo_switch' ,
24+ } as const
25+
1726 private constructor ( ) {
1827 // 从 localStorage 恢复权限信息
1928 this . loadFromStorage ( )
2029 }
2130
31+ /**
32+ * 当前登录身份指纹(用于避免“切换账号/登出后仍复用旧权限缓存”)
33+ * - 测试账号:localStorage.token = { token: email }
34+ * - GitHub OAuth:github_user / github_token
35+ * - 开发覆盖:user_role(会影响 mock 权限计算)
36+ */
37+ private getCurrentAuthKey ( ) : string {
38+ const safeRead = ( key : string ) => {
39+ try {
40+ return localStorage . getItem ( key ) || ''
41+ } catch {
42+ return ''
43+ }
44+ }
45+
46+ const roleOverride = safeRead ( this . STORAGE_KEYS . ROLE_OVERRIDE )
47+
48+ const githubUser = safeRead ( 'github_user' )
49+ const githubToken = safeRead ( 'github_token' )
50+ if ( githubUser ) {
51+ try {
52+ const obj = JSON . parse ( githubUser )
53+ const email = obj ?. email || ''
54+ const login = obj ?. login || ''
55+ const id = obj ?. id || ''
56+ return `github:${ email || login || id } ${ roleOverride ? `|role:${ roleOverride } ` : '' } `
57+ } catch {
58+ return `github:${ githubUser } ${ roleOverride ? `|role:${ roleOverride } ` : '' } `
59+ }
60+ }
61+ if ( githubToken ) {
62+ return `githubToken:${ githubToken } ${ roleOverride ? `|role:${ roleOverride } ` : '' } `
63+ }
64+
65+ // 测试账号 token
66+ const rawToken = safeRead ( 'token' )
67+ if ( rawToken ) {
68+ try {
69+ const obj = JSON . parse ( rawToken )
70+ const token = obj ?. token || rawToken
71+ return `token:${ token } ${ roleOverride ? `|role:${ roleOverride } ` : '' } `
72+ } catch {
73+ return `token:${ rawToken } ${ roleOverride ? `|role:${ roleOverride } ` : '' } `
74+ }
75+ }
76+
77+ // 未登录但存在 roleOverride 的情况(演示页)
78+ if ( roleOverride ) {
79+ return `anonymous|role:${ roleOverride } `
80+ }
81+
82+ return 'anonymous'
83+ }
84+
2285 static getInstance ( ) : PermissionService {
2386 if ( ! PermissionService . instance ) {
2487 PermissionService . instance = new PermissionService ( )
@@ -31,8 +94,22 @@ class PermissionService {
3194 */
3295 private loadFromStorage ( ) : void {
3396 try {
34- const stored = localStorage . getItem ( 'user_permissions' )
35- const lastFetch = localStorage . getItem ( 'permissions_fetch_time' )
97+ const stored = localStorage . getItem ( this . STORAGE_KEYS . PERMISSIONS )
98+ const lastFetch = localStorage . getItem ( this . STORAGE_KEYS . FETCH_TIME )
99+ const storedAuthKey = localStorage . getItem ( this . STORAGE_KEYS . AUTH_KEY )
100+ const currentAuthKey = this . getCurrentAuthKey ( )
101+
102+ // 如果登录身份发生变化,直接丢弃旧缓存,避免残留旧权限
103+ if ( storedAuthKey && storedAuthKey !== currentAuthKey ) {
104+ this . clearCache ( )
105+ return
106+ }
107+
108+ // 兼容旧版本:若存在权限缓存但没有 authKey,视为不可信,直接丢弃
109+ if ( ! storedAuthKey && stored ) {
110+ this . clearCache ( )
111+ return
112+ }
36113
37114 if ( stored && lastFetch ) {
38115 this . userPermissions = JSON . parse ( stored )
@@ -55,8 +132,9 @@ class PermissionService {
55132 */
56133 private saveToStorage ( permissions : UserPermission ) : void {
57134 try {
58- localStorage . setItem ( 'user_permissions' , JSON . stringify ( permissions ) )
59- localStorage . setItem ( 'permissions_fetch_time' , Date . now ( ) . toString ( ) )
135+ localStorage . setItem ( this . STORAGE_KEYS . PERMISSIONS , JSON . stringify ( permissions ) )
136+ localStorage . setItem ( this . STORAGE_KEYS . FETCH_TIME , Date . now ( ) . toString ( ) )
137+ localStorage . setItem ( this . STORAGE_KEYS . AUTH_KEY , this . getCurrentAuthKey ( ) )
60138 this . userPermissions = permissions
61139 this . lastFetchTime = Date . now ( )
62140 } catch ( error ) {
@@ -70,8 +148,23 @@ class PermissionService {
70148 clearCache ( ) : void {
71149 this . userPermissions = null
72150 this . lastFetchTime = 0
73- localStorage . removeItem ( 'user_permissions' )
74- localStorage . removeItem ( 'permissions_fetch_time' )
151+ localStorage . removeItem ( this . STORAGE_KEYS . PERMISSIONS )
152+ localStorage . removeItem ( this . STORAGE_KEYS . FETCH_TIME )
153+ localStorage . removeItem ( this . STORAGE_KEYS . AUTH_KEY )
154+ }
155+
156+ /**
157+ * 登出时清理(仅清理“权限相关”本地状态,不直接清理 token)
158+ * - 解决:登录/登出或 401 跳转时权限缓存残留,导致下个账号复用旧权限
159+ */
160+ logoutCleanup ( ) : void {
161+ this . clearCache ( )
162+ try {
163+ localStorage . removeItem ( this . STORAGE_KEYS . ROLE_OVERRIDE )
164+ localStorage . removeItem ( this . STORAGE_KEYS . FORCE_DEMO_SWITCH )
165+ } catch ( e ) {
166+ console . warn ( '清理权限相关本地覆盖失败:' , e )
167+ }
75168 }
76169
77170 /**
@@ -80,6 +173,18 @@ class PermissionService {
80173 * @param userId 用户ID(可选,用于获取特定用户的权限)
81174 */
82175 async getPermissions ( forceRefresh : boolean = false , userId ?: string ) : Promise < UserPermission > {
176+ // 若登录身份发生变化,强制清理旧缓存并重新获取
177+ try {
178+ const storedAuthKey = localStorage . getItem ( this . STORAGE_KEYS . AUTH_KEY )
179+ const currentAuthKey = this . getCurrentAuthKey ( )
180+ if ( storedAuthKey && storedAuthKey !== currentAuthKey ) {
181+ this . clearCache ( )
182+ forceRefresh = true
183+ }
184+ } catch {
185+ // ignore
186+ }
187+
83188 // 如果强制刷新或缓存过期,重新获取
84189 const now = Date . now ( )
85190 const isExpired = now - this . lastFetchTime > this . cacheExpireTime
0 commit comments