From 4521212a90d69dfdf2fdbe2eacd9e870364ade1c Mon Sep 17 00:00:00 2001 From: chenchun Date: Thu, 6 Nov 2025 11:29:21 +0800 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E7=BC=93=E5=AD=98=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在 Yi.Framework.Rbac.Application.Services.FileService 中注入 IMemoryCache,用于缓存文件元数据,减少对仓储的重复读取。 - 在 Get 方法中通过 key "File:{code}" 缓存 FileCacheItem,设置绝对过期时间为 1 天。 - 缓存项使用 Mapster 适配为 FileCacheItem,再适配回 FileAggregateRoot(保留现有逻辑判断和路径获取)。 - 新增缓存模型 Yi.Framework.Rbac.Domain.Shared.Caches.FileCacheItem(包含 Id、FileSize、FileName、FilePath、创建/修改信息等)。 - 增加并调整相关 using 引用(Microsoft.Extensions.Caching.Memory、Volo.Abp.Caching、Domain.Shared.Caches)。 - 同时修复了保存多文件时的缩进/空格格式(不影响功能)。 --- .../Services/FileService.cs | 27 ++++++++++++----- .../Caches/FileCacheItem.cs | 29 +++++++++++++++++++ 2 files changed, 49 insertions(+), 7 deletions(-) create mode 100644 Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Domain.Shared/Caches/FileCacheItem.cs diff --git a/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Application/Services/FileService.cs b/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Application/Services/FileService.cs index 90d6b5ef..7a67e6ae 100644 --- a/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Application/Services/FileService.cs +++ b/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Application/Services/FileService.cs @@ -6,8 +6,10 @@ using System.Threading.Tasks; using Mapster; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Caching.Memory; using Volo.Abp; using Volo.Abp.Application.Services; +using Volo.Abp.Caching; using Volo.Abp.Domain.Repositories; using Volo.Abp.Guids; using Yi.Framework.Core.Enums; @@ -16,6 +18,7 @@ using Yi.Framework.Rbac.Application.Contracts.Dtos.FileManager; using Yi.Framework.Rbac.Application.Contracts.IServices; using Yi.Framework.Rbac.Domain.Entities; using Yi.Framework.Rbac.Domain.Managers; +using Yi.Framework.Rbac.Domain.Shared.Caches; namespace Yi.Framework.Rbac.Application.Services { @@ -23,11 +26,13 @@ namespace Yi.Framework.Rbac.Application.Services { private readonly IRepository _repository; private readonly FileManager _fileManager; + private readonly IMemoryCache _memoryCache; - public FileService(IRepository repository, FileManager fileManager) + public FileService(IRepository repository, FileManager fileManager, IMemoryCache memoryCache) { _repository = repository; _fileManager = fileManager; + _memoryCache = memoryCache; } /// @@ -37,7 +42,14 @@ namespace Yi.Framework.Rbac.Application.Services [Route("file/{code}/{isThumbnail?}")] public async Task Get([FromRoute] Guid code, [FromRoute] bool? isThumbnail) { - var file = await _repository.GetAsync(x => x.Id == code); + var fileCache = await _memoryCache.GetOrCreateAsync($"File:{code}", async (options) => + { + options.AbsoluteExpiration = DateTime.Now.AddDays(1); + var file = await _repository.GetAsync(x => x.Id == code); + if (file == null!) return null; + return file.Adapt(); + }); + var file = fileCache?.Adapt(); var path = file?.GetQueryFileSavePath(isThumbnail); if (path is null || !File.Exists(path)) { @@ -58,12 +70,13 @@ namespace Yi.Framework.Rbac.Application.Services for (int i = 0; i < file.Count; i++) { - var entity= entities[i]; - using (var steam = file[i].OpenReadStream()) - { - await _fileManager.SaveFileAsync(entity,steam); - } + var entity = entities[i]; + using (var steam = file[i].OpenReadStream()) + { + await _fileManager.SaveFileAsync(entity, steam); + } } + return entities.Adapt>(); } } diff --git a/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Domain.Shared/Caches/FileCacheItem.cs b/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Domain.Shared/Caches/FileCacheItem.cs new file mode 100644 index 00000000..a8bfe3ab --- /dev/null +++ b/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.Domain.Shared/Caches/FileCacheItem.cs @@ -0,0 +1,29 @@ +namespace Yi.Framework.Rbac.Domain.Shared.Caches; + +public class FileCacheItem +{ + public Guid Id { get; set; } + + /// + /// 文件大小 + /// + public decimal FileSize { get; set; } + + /// + /// 文件名 + /// + public string FileName { get; set; } + + /// + /// 文件路径 + /// + public string FilePath { get; set; } + + public DateTime CreationTime { get; set; } + + public Guid? CreatorId { get; set; } + + public Guid? LastModifierId { get; set; } + + public DateTime? LastModificationTime { get; set; } +} \ No newline at end of file From 2ec7b5f4fd396a0510f539f3c6a3d53a6f92495e Mon Sep 17 00:00:00 2001 From: chenchun Date: Thu, 13 Nov 2025 16:49:44 +0800 Subject: [PATCH 2/3] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E8=BD=AF=E5=88=A0?= =?UTF-8?q?=E9=99=A4=E6=97=B6=E7=A9=BA=E5=BC=95=E7=94=A8=E5=BC=82=E5=B8=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 在 SqlSugarRepository.cs 中对 ISoftDelete 分支增加 GetByIdAsync 返回 null 的判断,避免在实体为 null 时继续反射赋值导致 NullReferenceException。若实体不存在,直接返回 false。 --- .../Yi.Framework.SqlSugarCore/Repositories/SqlSugarRepository.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/Repositories/SqlSugarRepository.cs b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/Repositories/SqlSugarRepository.cs index 7468ee9b..9243e5ed 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/Repositories/SqlSugarRepository.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/Repositories/SqlSugarRepository.cs @@ -264,6 +264,7 @@ namespace Yi.Framework.SqlSugarCore.Repositories if (typeof(ISoftDelete).IsAssignableFrom(typeof(TEntity))) { var entity = await GetByIdAsync(id); + if (entity == null) return false; //反射赋值 ReflexHelper.SetModelValue(nameof(ISoftDelete.IsDeleted), true, entity); return await UpdateAsync(entity); From 5eaffe2ec2bd74588338b71e73c68e097d5893d5 Mon Sep 17 00:00:00 2001 From: chenchun Date: Mon, 17 Nov 2025 11:19:15 +0800 Subject: [PATCH 3/3] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E5=B9=B6=E5=8F=91=E4=B9=90=E8=A7=82=E9=94=81=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E4=B8=8E=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在 DbConnOptions 新增 EnabledConcurrencyException(bool,默认 false) 配置项。 - 在 SqlSugarRepository 引入 IAbpLazyServiceProvider,通过 IOptions 延迟获取配置。 - UpdateAsync 改为仅当 EnabledConcurrencyException 为 true 且实体实现 IHasConcurrencyStamp 时,使用 ExecuteCommandWithOptLockAsync 并捕获 VersionExceptions 抛出 AbpDbConcurrencyException;否则回退到原有的 UpdateAsync 实现。 - 清理/调整部分 using 引用,新增 Microsoft.Extensions.Options 与 Volo.Abp.DependencyInjection 引用。 注意:默认值为 false,需在配置中显式开启 EnabledConcurrencyException 才会启用乐观并发校验,开启后会改变之前对带版本实体自动使用乐观锁的行为。 --- .../DbConnOptions.cs | 5 +++ .../Repositories/SqlSugarRepository.cs | 36 ++++++++++--------- Yi.Abp.Net8/src/Yi.Abp.Web/appsettings.json | 3 +- 3 files changed, 27 insertions(+), 17 deletions(-) diff --git a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore.Abstractions/DbConnOptions.cs b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore.Abstractions/DbConnOptions.cs index 6f48f14d..65c83f13 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore.Abstractions/DbConnOptions.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore.Abstractions/DbConnOptions.cs @@ -58,5 +58,10 @@ namespace Yi.Framework.SqlSugarCore.Abstractions /// 是否启用SaaS多租户 /// public bool EnabledSaasMultiTenancy { get; set; } = false; + + /// + /// 是否开启更新并发乐观锁 + /// + public bool EnabledConcurrencyException { get;set; } = false; } } \ No newline at end of file diff --git a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/Repositories/SqlSugarRepository.cs b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/Repositories/SqlSugarRepository.cs index 9243e5ed..4b3b7023 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/Repositories/SqlSugarRepository.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/Repositories/SqlSugarRepository.cs @@ -1,12 +1,9 @@ -using System.Linq; -using System.Linq.Expressions; -using System.Text; -using Microsoft.Extensions.Logging; +using System.Linq.Expressions; +using Microsoft.Extensions.Options; using Nito.AsyncEx; using SqlSugar; -using Volo.Abp; -using Volo.Abp.Auditing; using Volo.Abp.Data; +using Volo.Abp.DependencyInjection; using Volo.Abp.Domain.Entities; using Volo.Abp.Domain.Repositories; using Volo.Abp.Linq; @@ -23,6 +20,9 @@ namespace Yi.Framework.SqlSugarCore.Repositories private readonly ISugarDbContextProvider _dbContextProvider; + public IAbpLazyServiceProvider LazyServiceProvider { get; set; } + + protected DbConnOptions? Options => LazyServiceProvider?.LazyGetService>().Value; /// /// 异步查询执行器 /// @@ -380,20 +380,24 @@ namespace Yi.Framework.SqlSugarCore.Repositories public virtual async Task UpdateAsync(TEntity updateObj) { - if (typeof(TEntity).IsAssignableTo())//带版本号乐观锁更新 + if (Options is not null && Options.EnabledConcurrencyException) { - try + if (typeof(TEntity).IsAssignableTo()) //带版本号乐观锁更新 { - int num = await (await GetDbSimpleClientAsync()) - .Context.Updateable(updateObj).ExecuteCommandWithOptLockAsync(true); - return num>0; - } - catch (VersionExceptions ex) - { - - throw new AbpDbConcurrencyException($"{ex.Message}[更新失败:ConcurrencyStamp不是最新版本],entityInfo:{updateObj}", ex); + try + { + int num = await (await GetDbSimpleClientAsync()) + .Context.Updateable(updateObj).ExecuteCommandWithOptLockAsync(true); + return num > 0; + } + catch (VersionExceptions ex) + { + throw new AbpDbConcurrencyException( + $"{ex.Message}[更新失败:ConcurrencyStamp不是最新版本],entityInfo:{updateObj}", ex); + } } } + return await (await GetDbSimpleClientAsync()).UpdateAsync(updateObj); } diff --git a/Yi.Abp.Net8/src/Yi.Abp.Web/appsettings.json b/Yi.Abp.Net8/src/Yi.Abp.Web/appsettings.json index 1b1eee0e..ba5ec320 100644 --- a/Yi.Abp.Net8/src/Yi.Abp.Web/appsettings.json +++ b/Yi.Abp.Net8/src/Yi.Abp.Web/appsettings.json @@ -40,7 +40,8 @@ "EnabledDbSeed": true, "EnableUnderLine": false, // 启用驼峰转下划线 //SAAS多租户 - "EnabledSaasMultiTenancy": true + "EnabledSaasMultiTenancy": true, + "EnabledConcurrencyException": false //读写分离地址 //"ReadUrl": [ // "DataSource=[xxxx]", //Sqlite