diff --git a/Admin.NET/Admin.NET.Core/Service/Auth/SysAuthService.cs b/Admin.NET/Admin.NET.Core/Service/Auth/SysAuthService.cs index d35f2b5a..0394d3df 100644 --- a/Admin.NET/Admin.NET.Core/Service/Auth/SysAuthService.cs +++ b/Admin.NET/Admin.NET.Core/Service/Auth/SysAuthService.cs @@ -440,4 +440,15 @@ public class SysAuthService : IDynamicApiController, ITransient return 401; } } + + /// + /// 刷新token + /// + /// + [NonAction] + public async Task RefreshToken(long userId) + { + var user = await _sysUserRep.AsQueryable().IgnoreTenant().Includes(u => u.SysOrg).FirstAsync(u => u.Id == userId); + await CreateToken(user); + } } \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Cache/SysCacheService.cs b/Admin.NET/Admin.NET.Core/Service/Cache/SysCacheService.cs index efc3a340..8dcb366f 100644 --- a/Admin.NET/Admin.NET.Core/Service/Cache/SysCacheService.cs +++ b/Admin.NET/Admin.NET.Core/Service/Cache/SysCacheService.cs @@ -457,6 +457,17 @@ public class SysCacheService : IDynamicApiController, ISingleton return _cacheProvider.Cache.ContainsKey($"{_cacheOptions.Prefix}{key}"); } + /// + /// 检查缓存是否不存在 + /// + /// 键 + /// + [NonAction] + public bool NotExistKey(string key) + { + return !_cacheProvider.Cache.ContainsKey($"{_cacheOptions.Prefix}{key}"); + } + /// /// 根据键名前缀删除缓存 🔖 /// diff --git a/Admin.NET/Admin.NET.Core/Service/User/SysUserService.cs b/Admin.NET/Admin.NET.Core/Service/User/SysUserService.cs index caf7c9fa..8efd83a5 100644 --- a/Admin.NET/Admin.NET.Core/Service/User/SysUserService.cs +++ b/Admin.NET/Admin.NET.Core/Service/User/SysUserService.cs @@ -168,6 +168,9 @@ public class SysUserService : IDynamicApiController, ITransient // 更新域账号 await _sysUserLdapService.AddUserLdap(user.TenantId!.Value, user.Id, user.Account, input.DomainAccount); + // 清除用户session + _userManager.RemoveSession(input.Id); + // 发布更新用户事件 await _eventPublisher.PublishAsync(UserEventTypeEnum.Update, input); } @@ -246,8 +249,11 @@ public class SysUserService : IDynamicApiController, ITransient [DisplayName("更新用户基本信息")] public virtual async Task UpdateBaseInfo(SysUser user) { - return await _sysUserRep.AsUpdateable(user) + var count = await _sysUserRep.AsUpdateable(user) .IgnoreColumns(u => new { u.CreateTime, u.Account, u.Password, u.AccountType, u.OrgId, u.PosId }).ExecuteCommandAsync(); + // 清除用户session + _userManager.RemoveSession(user.Id); + return count; } /// @@ -492,5 +498,8 @@ public class SysUserService : IDynamicApiController, ITransient // 强制下线账号 await _sysOnlineUserService.ForceOfflineByUserId(user.Id); + + // 清除用户session + _userManager.RemoveSession(user.Id); } } \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/User/UserManager.cs b/Admin.NET/Admin.NET.Core/Service/User/UserManager.cs index 3c68fd0e..a408753f 100644 --- a/Admin.NET/Admin.NET.Core/Service/User/UserManager.cs +++ b/Admin.NET/Admin.NET.Core/Service/User/UserManager.cs @@ -27,7 +27,14 @@ public class UserManager( /// [System.Text.Json.Serialization.JsonIgnore] [Newtonsoft.Json.JsonIgnore] - protected virtual UserSessionDao Session => _session ??= sysCacheService.Get(CacheConst.KeyUserSession + UserId); + protected virtual UserSessionDao Session + { + get + { + if (_session == null || _session.UserId != UserId) _session = sysCacheService.Get(CacheConst.KeyUserSession + UserId); + return _session; + } + } /// /// 用户Id diff --git a/Admin.NET/Admin.NET.Web.Core/Handlers/JwtHandler.cs b/Admin.NET/Admin.NET.Web.Core/Handlers/JwtHandler.cs index 8ea1021c..bc5fef16 100644 --- a/Admin.NET/Admin.NET.Web.Core/Handlers/JwtHandler.cs +++ b/Admin.NET/Admin.NET.Web.Core/Handlers/JwtHandler.cs @@ -62,7 +62,7 @@ namespace Admin.NET.Web.Core // 验证Token黑名单 var userId = httpContext.User.FindFirst(ClaimConst.UserId)?.Value; var version = httpContext.User.FindFirst(ClaimConst.TokenVersion)?.Value; - if (sysCacheService.ExistKey($"{CacheConst.KeyTokenBlacklist}{userId}:{version}") || !sysCacheService.ExistKey($"{CacheConst.KeyUserSession}{userId}")) + if (sysCacheService.ExistKey($"{CacheConst.KeyTokenBlacklist}{userId}:{version}")) { context.Fail(new AuthorizationFailureReason(this, "令牌已失效,请重新登录。")); context.StatusCode(StatusCodes.Status401Unauthorized); @@ -70,6 +70,14 @@ namespace Admin.NET.Web.Core return; } + // 刷新 Session + if (sysCacheService.NotExistKey($"{CacheConst.KeyUserSession}{userId}")) + { + var sysAuthService = serviceScope.ServiceProvider.GetRequiredService(); + await sysAuthService.RefreshToken(long.Parse(userId!)); + return; + } + if (!string.IsNullOrWhiteSpace(userId)) { // 查库并缓存用户Token版本 diff --git a/Web/src/types/pinia.d.ts b/Web/src/types/pinia.d.ts index ad101822..1a372c0f 100644 --- a/Web/src/types/pinia.d.ts +++ b/Web/src/types/pinia.d.ts @@ -46,6 +46,7 @@ declare interface RoutesListState { // 布局配置 declare interface ThemeConfigState { themeConfig: { + serverTime?: Date; // 服务器时间 isDrawer: boolean; // 是否开启抽屉配置 primary: string; // 主题颜色 topBar: string; // 顶部栏背景 diff --git a/Web/src/utils/axios-utils.ts b/Web/src/utils/axios-utils.ts index 2331a9c1..343ae73f 100644 --- a/Web/src/utils/axios-utils.ts +++ b/Web/src/utils/axios-utils.ts @@ -11,6 +11,11 @@ import { BaseAPI, BASE_PATH } from '../api-services/system/base'; import { ElMessage } from 'element-plus'; import { Local, Session } from '../utils/storage'; +import {useThemeConfig} from "/@/stores/themeConfig"; +import {storeToRefs} from "pinia"; + +const storesThemeConfig = useThemeConfig(); +const { themeConfig } = storeToRefs(storesThemeConfig); // 接口服务器配置 export const serveConfig = new Configuration({ @@ -66,7 +71,7 @@ axiosInstance.interceptors.request.use( const exp = getJWTDate(jwt.exp as number); // token 已经过期 - if (new Date() >= exp) { + if (themeConfig.value.serverTime && themeConfig.value.serverTime >= exp) { // 获取刷新 token const refreshAccessToken = Local.get(refreshAccessTokenKey); @@ -103,6 +108,9 @@ axiosInstance.interceptors.request.use( // axios 响应拦截 axiosInstance.interceptors.response.use( (res) => { + // 记录服务器时间 + themeConfig.value.serverTime = res.data.time ? new Date(res.data.time) : undefined; + // 获取状态码和返回数据 var status = res.status; var serve = res.data; @@ -241,4 +249,4 @@ export function getJWTDate(timestamp: number): Date { */ export function sleep(delay: number) { return new Promise((resolve) => setTimeout(resolve, delay)); -} +} \ No newline at end of file diff --git a/Web/src/utils/sysInfo.ts b/Web/src/utils/sysInfo.ts index 07919dee..ecff715a 100644 --- a/Web/src/utils/sysInfo.ts +++ b/Web/src/utils/sysInfo.ts @@ -65,6 +65,8 @@ export async function loadSysInfo(tenantid: number) { themeConfig.value.onlineNotice = data.onlineNotice; // 密码加解密公匙 window.__env__.VITE_SM_PUBLIC_KEY = data.publicKey; + // 服务器时间 + themeConfig.value.serverTime = res.data.time ? new Date(Date.parse(res.data.time as any)) : undefined; // 更新 favicon updateFavicon(data.logo);