auth.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  1. // 认证相关工具函数
  2. const LOGIN_FORM_KEY = 'loginForm';
  3. const TENANT_ID_KEY = 'tenantId';
  4. const TOKEN_KEY = 'token';
  5. const USER_ID_KEY = 'userId';
  6. const ACCESS_TOKEN_KEY = 'ACCESS_TOKEN';
  7. const REFRESH_TOKEN_KEY = 'REFRESH_TOKEN';
  8. const USER_KEY = 'user';
  9. const ROLE_ROUTERS_KEY = 'roleRouters';
  10. const LANG_KEY = 'lang';
  11. const IS_DARK_KEY = 'isDark';
  12. const HM_LVT_KEY = 'Hm_lvt_a1ff8825baa73c3a78eb96aa40325abc';
  13. // 登录表单缓存类型
  14. export interface LoginFormCache {
  15. username?: string;
  16. password?: string;
  17. tenantName?: string;
  18. rememberMe?: boolean;
  19. }
  20. // 设置登录表单缓存
  21. export const setLoginForm = (form: LoginFormCache) => {
  22. localStorage.setItem(LOGIN_FORM_KEY, JSON.stringify(form));
  23. };
  24. // 获取登录表单缓存
  25. export const getLoginForm = (): LoginFormCache | null => {
  26. const form = localStorage.getItem(LOGIN_FORM_KEY);
  27. return form ? JSON.parse(form) : null;
  28. };
  29. // 移除登录表单缓存
  30. export const removeLoginForm = () => {
  31. localStorage.removeItem(LOGIN_FORM_KEY);
  32. };
  33. // 设置租户ID
  34. export const setTenantId = (tenantId: string | number) => {
  35. localStorage.setItem(TENANT_ID_KEY, String(tenantId));
  36. };
  37. // 获取租户ID
  38. export const getTenantId = (): string | null => {
  39. return localStorage.getItem(TENANT_ID_KEY);
  40. };
  41. // 移除租户ID
  42. export const removeTenantId = () => {
  43. localStorage.removeItem(TENANT_ID_KEY);
  44. };
  45. // 设置Token(兼容多种格式)
  46. export const setToken = (tokenData: { accessToken?: string; token?: string } | string) => {
  47. if (typeof tokenData === 'string') {
  48. localStorage.setItem(TOKEN_KEY, tokenData);
  49. } else {
  50. const token = tokenData.accessToken || tokenData.token || '';
  51. localStorage.setItem(TOKEN_KEY, token);
  52. }
  53. };
  54. // 获取Token
  55. export const getToken = (): string | null => {
  56. return localStorage.getItem(TOKEN_KEY);
  57. };
  58. // 移除Token
  59. export const removeToken = () => {
  60. localStorage.removeItem(TOKEN_KEY);
  61. };
  62. // 设置用户ID
  63. export const setUserId = (userId: string) => {
  64. localStorage.setItem(USER_ID_KEY, userId);
  65. };
  66. // 获取用户ID
  67. export const getUserId = (): string | null => {
  68. return localStorage.getItem(USER_ID_KEY);
  69. };
  70. // 移除用户ID
  71. export const removeUserId = () => {
  72. localStorage.removeItem(USER_ID_KEY);
  73. };
  74. // 设置ACCESS_TOKEN
  75. export const setAccessToken = (token: string) => {
  76. localStorage.setItem(ACCESS_TOKEN_KEY, token);
  77. };
  78. // 获取ACCESS_TOKEN
  79. export const getAccessToken = (): string | null => {
  80. return localStorage.getItem(ACCESS_TOKEN_KEY);
  81. };
  82. // 移除ACCESS_TOKEN
  83. export const removeAccessToken = () => {
  84. localStorage.removeItem(ACCESS_TOKEN_KEY);
  85. };
  86. // 设置REFRESH_TOKEN
  87. export const setRefreshToken = (token: string) => {
  88. localStorage.setItem(REFRESH_TOKEN_KEY, token);
  89. };
  90. // 获取REFRESH_TOKEN
  91. export const getRefreshToken = (): string | null => {
  92. return localStorage.getItem(REFRESH_TOKEN_KEY);
  93. };
  94. // 移除REFRESH_TOKEN
  95. export const removeRefreshToken = () => {
  96. localStorage.removeItem(REFRESH_TOKEN_KEY);
  97. };
  98. // 设置用户信息
  99. export const setUser = (user: any) => {
  100. localStorage.setItem(USER_KEY, JSON.stringify(user));
  101. };
  102. // 获取用户信息
  103. export const getUser = (): any | null => {
  104. const user = localStorage.getItem(USER_KEY);
  105. return user ? JSON.parse(user) : null;
  106. };
  107. // 移除用户信息
  108. export const removeUser = () => {
  109. localStorage.removeItem(USER_KEY);
  110. };
  111. // 设置角色路由
  112. export const setRoleRouters = (routers: any) => {
  113. localStorage.setItem(ROLE_ROUTERS_KEY, JSON.stringify(routers));
  114. };
  115. // 获取角色路由
  116. export const getRoleRouters = (): any | null => {
  117. const routers = localStorage.getItem(ROLE_ROUTERS_KEY);
  118. return routers ? JSON.parse(routers) : null;
  119. };
  120. // 移除角色路由
  121. export const removeRoleRouters = () => {
  122. localStorage.removeItem(ROLE_ROUTERS_KEY);
  123. };
  124. // 设置语言
  125. export const setLang = (lang: string) => {
  126. localStorage.setItem(LANG_KEY, lang);
  127. };
  128. // 获取语言
  129. export const getLang = (): string | null => {
  130. return localStorage.getItem(LANG_KEY);
  131. };
  132. // 移除语言
  133. export const removeLang = () => {
  134. localStorage.removeItem(LANG_KEY);
  135. };
  136. // 设置暗色模式
  137. export const setIsDark = (isDark: boolean) => {
  138. localStorage.setItem(IS_DARK_KEY, String(isDark));
  139. };
  140. // 获取暗色模式
  141. export const getIsDark = (): boolean => {
  142. const isDark = localStorage.getItem(IS_DARK_KEY);
  143. return isDark === 'true';
  144. };
  145. // 移除暗色模式
  146. export const removeIsDark = () => {
  147. localStorage.removeItem(IS_DARK_KEY);
  148. };
  149. // 设置Hm_lvt cookie值
  150. export const setHmLvt = (value: string) => {
  151. localStorage.setItem(HM_LVT_KEY, value);
  152. };
  153. // 获取Hm_lvt cookie值
  154. export const getHmLvt = (): string | null => {
  155. return localStorage.getItem(HM_LVT_KEY);
  156. };
  157. // 移除Hm_lvt cookie值
  158. export const removeHmLvt = () => {
  159. localStorage.removeItem(HM_LVT_KEY);
  160. };
  161. // 清除所有认证信息
  162. export const clearAuth = () => {
  163. removeToken();
  164. removeTenantId();
  165. removeUserId();
  166. removeLoginForm();
  167. removeAccessToken();
  168. removeRefreshToken();
  169. removeUser();
  170. removeRoleRouters();
  171. removeHmLvt();
  172. // 清除最后活跃菜单缓存
  173. sessionStorage.removeItem('lastActiveMenu');
  174. // 清除权限信息
  175. try {
  176. const { clearPermissionInfo } = require('./permission');
  177. clearPermissionInfo();
  178. } catch (e) {
  179. // 如果权限模块未加载,忽略错误
  180. }
  181. };
  182. // Token定期检测和刷新
  183. let tokenCheckInterval: NodeJS.Timeout | null = null;
  184. let isChecking = false; // 防止并发检测
  185. export const startTokenCheck = (intervalSeconds: number = 30) => {
  186. console.log(`[TokenCheck] 启动token检测,间隔: ${intervalSeconds}秒`);
  187. // 如果已经有定时器在运行,先清除
  188. if (tokenCheckInterval) {
  189. console.log('[TokenCheck] 清除旧的定时器');
  190. stopTokenCheck();
  191. }
  192. // 立即执行一次检测
  193. console.log('[TokenCheck] 立即执行一次检测');
  194. checkTokenAndRefresh();
  195. // 设置定时器
  196. tokenCheckInterval = setInterval(() => {
  197. console.log(`[TokenCheck] 定时器触发,执行检测 (间隔: ${intervalSeconds}秒)`);
  198. checkTokenAndRefresh();
  199. }, intervalSeconds * 1000);
  200. console.log(`[TokenCheck] 定时器已设置,ID: ${tokenCheckInterval}`);
  201. };
  202. export const stopTokenCheck = () => {
  203. if (tokenCheckInterval) {
  204. clearInterval(tokenCheckInterval);
  205. tokenCheckInterval = null;
  206. }
  207. };
  208. // 检测token并刷新
  209. const checkTokenAndRefresh = async () => {
  210. console.log('[TokenCheck] checkTokenAndRefresh 被调用');
  211. // 如果正在检测中,跳过
  212. if (isChecking) {
  213. console.log('[TokenCheck] 正在检测中,跳过本次调用');
  214. return;
  215. }
  216. // 如果没有refreshToken,不需要检测
  217. const refreshToken = getRefreshToken();
  218. if (!refreshToken) {
  219. console.log('[TokenCheck] 没有refreshToken,跳过检测');
  220. return;
  221. }
  222. console.log('[TokenCheck] 开始检测token...');
  223. isChecking = true;
  224. try {
  225. // 动态导入loginApi和axios,避免循环依赖
  226. const { loginApi } = await import('../api/Login');
  227. const axios = (await import('axios')).default;
  228. const { env } = await import('./env');
  229. // 直接使用axios调用,绕过拦截器,获取原始响应
  230. const baseURL = env.baseUrl ? `${env.baseUrl}/admin-api` : '/admin-api';
  231. const token = getAccessToken() || getToken();
  232. if (!token) {
  233. // 如果没有token,直接尝试刷新
  234. console.log('[TokenCheck] 未找到accessToken,尝试刷新token...');
  235. await refreshTokenIfNeeded(refreshToken);
  236. return;
  237. }
  238. const url = `${baseURL}/system/notify-message/get-unread-count`;
  239. console.log(`[TokenCheck] 调用检测接口: ${url}`);
  240. const response = await axios.get(url, {
  241. headers: {
  242. 'Authorization': `Bearer ${token}`,
  243. 'Content-Type': 'application/json',
  244. },
  245. validateStatus: (status) => {
  246. // 接受所有状态码,包括401,以便我们能够检测到登录状态失效
  247. return status >= 200 && status < 500;
  248. },
  249. });
  250. console.log(`[TokenCheck] 接口响应: status=${response.status}, code=${response.data?.code}`);
  251. // 检查响应数据,如果code是401或HTTP状态码是401,说明token失效
  252. const responseData = response.data;
  253. const isTokenExpired =
  254. response.status === 401 ||
  255. (responseData && (responseData.code === 401 || responseData.msg === '账号未登录'));
  256. if (isTokenExpired) {
  257. console.log('[TokenCheck] 检测到token失效,开始刷新token...');
  258. await refreshTokenIfNeeded(refreshToken);
  259. } else {
  260. console.log('[TokenCheck] Token有效,无需刷新');
  261. }
  262. // 如果返回正常,说明token有效,不需要处理
  263. } catch (error: any) {
  264. console.log(`[TokenCheck] 接口调用异常:`, error?.message || error);
  265. // 检查错误响应,如果是401,说明token失效
  266. const isTokenExpired =
  267. error?.response?.status === 401 ||
  268. (error?.response?.data && (
  269. error.response.data.code === 401 ||
  270. error.response.data.msg === '账号未登录'
  271. ));
  272. if (isTokenExpired) {
  273. console.log('[TokenCheck] 检测到token失效(通过错误响应),开始刷新token...');
  274. await refreshTokenIfNeeded(refreshToken);
  275. } else {
  276. // 其他错误(网络问题等),不处理,不退出登录
  277. console.log('[TokenCheck] Token检测接口调用失败:', error?.message || error, '但不退出登录');
  278. }
  279. } finally {
  280. isChecking = false;
  281. console.log('[TokenCheck] 检测完成');
  282. }
  283. };
  284. // 刷新token的辅助函数
  285. const refreshTokenIfNeeded = async (refreshToken: string) => {
  286. try {
  287. const { loginApi } = await import('../api/Login');
  288. const refreshResponse = await loginApi.refreshToken(refreshToken);
  289. // 处理返回数据格式:{code: 0, data: {accessToken, refreshToken, ...}, msg: ""}
  290. let tokenData;
  291. if (refreshResponse?.code === 0 && refreshResponse?.data) {
  292. // 标准格式:{code: 0, data: {...}}
  293. tokenData = refreshResponse.data;
  294. } else if (refreshResponse?.data) {
  295. // 兼容格式:{data: {...}}
  296. tokenData = refreshResponse.data;
  297. } else {
  298. // 直接是数据对象
  299. tokenData = refreshResponse;
  300. }
  301. const newAccessToken = tokenData?.accessToken || tokenData?.token;
  302. const newRefreshToken = tokenData?.refreshToken;
  303. if (newAccessToken) {
  304. // 更新token
  305. setToken({ accessToken: newAccessToken, token: newAccessToken });
  306. setAccessToken(newAccessToken);
  307. if (newRefreshToken) {
  308. setRefreshToken(newRefreshToken);
  309. }
  310. console.log('Token刷新成功');
  311. } else {
  312. console.warn('刷新token失败:未返回新token,但不退出登录');
  313. // 即使刷新失败,也不退出登录,继续保留当前页面
  314. }
  315. } catch (refreshError) {
  316. console.warn('刷新token失败:', refreshError, '但不退出登录,继续保留当前页面');
  317. // 即使刷新失败,也不退出登录,继续保留当前页面
  318. }
  319. };