🎨 缓存的完善优化

1.统一封装基于微软分布式缓存接口IDistributedCache使用
2.IDistributedCache只适合普通的缓存使用,如果要使用redis队列、订阅redis消息等,就要使用redis原生库
3.增加缓存管理接口[Systems/CacheManageController]
4.目前支持内存、redis缓存实现,理论可随意扩展甚至自定义实现
5.默认使用内存缓存,可在appsetting.json中配置Redis

切换到IDistributedCache好处如下
默认session使用IDistributedCache进行存储,如果你搭配使用IDistributedCache+外部缓存(如Redis),可实现应用程序重启session不丢失
更直观就是,调试的时候登录swagger后即使重启调试也无需在登陆
This commit is contained in:
LemonNoCry 2023-06-01 17:54:54 +08:00
parent 3f24902521
commit 7629527ee9
No known key found for this signature in database
27 changed files with 964 additions and 418 deletions

View File

@ -106,7 +106,6 @@
</ItemGroup>
<ItemGroup>
<Folder Include="Controllers\Systems\" />
<Folder Include="wwwroot\BlogCore.Data.excel\" />
</ItemGroup>

View File

@ -1299,6 +1299,41 @@
<param name="id"></param>
<returns></returns>
</member>
<member name="T:Blog.Core.Api.Controllers.Systems.CacheManageController">
<summary>
缓存管理
</summary>
</member>
<member name="M:Blog.Core.Api.Controllers.Systems.CacheManageController.Get">
<summary>
获取全部缓存
</summary>
<returns></returns>
</member>
<member name="M:Blog.Core.Api.Controllers.Systems.CacheManageController.Get(System.String)">
<summary>
获取缓存
</summary>
<returns></returns>
</member>
<member name="M:Blog.Core.Api.Controllers.Systems.CacheManageController.Post(System.String,System.String,System.Nullable{System.Int32})">
<summary>
新增
</summary>
<returns></returns>
</member>
<member name="M:Blog.Core.Api.Controllers.Systems.CacheManageController.Delete">
<summary>
删除全部缓存
</summary>
<returns></returns>
</member>
<member name="M:Blog.Core.Api.Controllers.Systems.CacheManageController.Delete(System.String)">
<summary>
删除缓存
</summary>
<returns></returns>
</member>
<member name="T:Blog.Core.Api.Controllers.Systems.DataBaseController">
<summary>
数据库管理

View File

@ -0,0 +1,81 @@
using Blog.Core.Common.Caches;
using Blog.Core.Controllers;
using Blog.Core.Model;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Blog.Core.Api.Controllers.Systems;
/// <summary>
/// 缓存管理
/// </summary>
[Route("api/Systems/[controller]")]
[ApiController]
[Authorize(Permissions.Name)]
public class CacheManageController : BaseApiController
{
private readonly ICaching _caching;
public CacheManageController(ICaching caching)
{
_caching = caching;
}
/// <summary>
/// 获取全部缓存
/// </summary>
/// <returns></returns>
[HttpGet]
public async Task<MessageModel<List<string>>> Get()
{
return Success(await _caching.GetAllCacheKeysAsync());
}
/// <summary>
/// 获取缓存
/// </summary>
/// <returns></returns>
[HttpGet("{key}")]
public async Task<MessageModel<string>> Get(string key)
{
return Success<string>(await _caching.GetStringAsync(key));
}
/// <summary>
/// 新增
/// </summary>
/// <returns></returns>
[HttpPost]
public async Task<MessageModel> Post([FromQuery] string key, [FromQuery] string value, [FromQuery] int? expire)
{
if (expire.HasValue)
await _caching.SetStringAsync(key, value, TimeSpan.FromMilliseconds(expire.Value));
else
await _caching.SetStringAsync(key, value);
return Success();
}
/// <summary>
/// 删除全部缓存
/// </summary>
/// <returns></returns>
[HttpDelete]
public async Task<MessageModel> Delete()
{
await _caching.RemoveAllAsync();
return Success();
}
/// <summary>
/// 删除缓存
/// </summary>
/// <returns></returns>
[Route("{key}")]
[HttpDelete]
public async Task<MessageModel> Delete(string key)
{
await _caching.RemoveAsync(key);
return Success();
}
}

View File

@ -57,8 +57,7 @@ RoutePrefix.Name = AppSettings.app(new string[] { "AppSettings", "SvcName" }).Ob
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
builder.Services.AddMemoryCacheSetup();
builder.Services.AddRedisCacheSetup();
builder.Services.AddCacheSetup();
builder.Services.AddSqlsugarSetup();
builder.Services.AddDbSetup();

View File

@ -8,6 +8,7 @@ using Blog.Core.Common.LogHelper;
using Blog.Core.Common.Seed;
using Blog.Core.Extensions;
using Blog.Core.Extensions.Middlewares;
using Blog.Core.Extensions.ServiceExtensions;
using Blog.Core.Filter;
using Blog.Core.Hubs;
using Blog.Core.IServices;
@ -49,9 +50,7 @@ namespace Blog.Core
// 确保从认证中心返回的ClaimType不被更改不使用Map映射
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.AddMemoryCacheSetup();
services.AddRedisCacheSetup();
services.AddCacheSetup();
services.AddSqlsugarSetup();
services.AddDbSetup();
services.AddAutoMapperSetup();

View File

@ -18,7 +18,9 @@
},
"AllowedHosts": "*",
"Redis": {
"ConnectionString": "127.0.0.1:6319,password=admin"
"Enable": true,
"ConnectionString": "127.0.0.1:6379",
"InstanceName": "" //
},
"RabbitMQ": {
"Enabled": false,

View File

@ -51,7 +51,6 @@
<ItemGroup>
<Folder Include="Core\" />
<Folder Include="Const\" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,328 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Blog.Core.Common.Const;
using Microsoft.Extensions.Caching.Distributed;
using Newtonsoft.Json;
namespace Blog.Core.Common.Caches;
public class Caching : ICaching
{
private readonly IDistributedCache _cache;
public Caching(IDistributedCache cache)
{
_cache = cache;
}
private byte[] GetBytes<T>(T source)
{
return Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(source));
}
public IDistributedCache Cache => _cache;
public void AddCacheKey(string cacheKey)
{
var res = _cache.GetString(CacheConst.KeyAll);
var allkeys = string.IsNullOrWhiteSpace(res) ? new List<string>() : JsonConvert.DeserializeObject<List<string>>(res);
if (!allkeys.Any(m => m == cacheKey))
{
allkeys.Add(cacheKey);
_cache.SetString(CacheConst.KeyAll, JsonConvert.SerializeObject(allkeys));
}
}
/// <summary>
/// 增加缓存Key
/// </summary>
/// <param name="cacheKey"></param>
/// <returns></returns>
public async Task AddCacheKeyAsync(string cacheKey)
{
var res = await _cache.GetStringAsync(CacheConst.KeyAll);
var allkeys = string.IsNullOrWhiteSpace(res) ? new List<string>() : JsonConvert.DeserializeObject<List<string>>(res);
if (!allkeys.Any(m => m == cacheKey))
{
allkeys.Add(cacheKey);
await _cache.SetStringAsync(CacheConst.KeyAll, JsonConvert.SerializeObject(allkeys));
}
}
public void DelByPattern(string key)
{
var allkeys = GetAllCacheKeys();
if (allkeys == null) return;
var delAllkeys = allkeys.Where(u => u.Contains(key)).ToList();
delAllkeys.ForEach(u => { _cache.Remove(u); });
// 更新所有缓存键
allkeys = allkeys.Where(u => !u.Contains(key)).ToList();
_cache.SetString(CacheConst.KeyAll, JsonConvert.SerializeObject(allkeys));
}
/// <summary>
/// 删除某特征关键字缓存
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public async Task DelByPatternAsync(string key)
{
var allkeys = await GetAllCacheKeysAsync();
if (allkeys == null) return;
var delAllkeys = allkeys.Where(u => u.Contains(key)).ToList();
delAllkeys.ForEach(u => { _cache.Remove(u); });
// 更新所有缓存键
allkeys = allkeys.Where(u => !u.Contains(key)).ToList();
await _cache.SetStringAsync(CacheConst.KeyAll, JsonConvert.SerializeObject(allkeys));
}
public void DelCacheKey(string cacheKey)
{
var res = _cache.GetString(CacheConst.KeyAll);
var allkeys = string.IsNullOrWhiteSpace(res) ? new List<string>() : JsonConvert.DeserializeObject<List<string>>(res);
if (allkeys.Any(m => m == cacheKey))
{
allkeys.Remove(cacheKey);
_cache.SetString(CacheConst.KeyAll, JsonConvert.SerializeObject(allkeys));
}
}
/// <summary>
/// 删除缓存
/// </summary>
/// <param name="cacheKey"></param>
/// <returns></returns>
public async Task DelCacheKeyAsync(string cacheKey)
{
var res = await _cache.GetStringAsync(CacheConst.KeyAll);
var allkeys = string.IsNullOrWhiteSpace(res) ? new List<string>() : JsonConvert.DeserializeObject<List<string>>(res);
if (allkeys.Any(m => m == cacheKey))
{
allkeys.Remove(cacheKey);
await _cache.SetStringAsync(CacheConst.KeyAll, JsonConvert.SerializeObject(allkeys));
}
}
public bool Exists(string cacheKey)
{
var res = _cache.Get(cacheKey);
return res != null;
}
/// <summary>
/// 检查给定 key 是否存在
/// </summary>
/// <param name="cacheKey">键</param>
/// <returns></returns>
public async Task<bool> ExistsAsync(string cacheKey)
{
var res = await _cache.GetAsync(cacheKey);
return res != null;
}
public List<string> GetAllCacheKeys()
{
var res = _cache.GetString(CacheConst.KeyAll);
return string.IsNullOrWhiteSpace(res) ? null : JsonConvert.DeserializeObject<List<string>>(res);
}
/// <summary>
/// 获取所有缓存列表
/// </summary>
/// <returns></returns>
public async Task<List<string>> GetAllCacheKeysAsync()
{
var res = await _cache.GetStringAsync(CacheConst.KeyAll);
return string.IsNullOrWhiteSpace(res) ? null : JsonConvert.DeserializeObject<List<string>>(res);
}
public T Get<T>(string cacheKey)
{
var res = _cache.Get(cacheKey);
return res == null ? default : JsonConvert.DeserializeObject<T>(Encoding.UTF8.GetString(res));
}
/// <summary>
/// 获取缓存
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="cacheKey"></param>
/// <returns></returns>
public async Task<T> GetAsync<T>(string cacheKey)
{
var res = await _cache.GetAsync(cacheKey);
return res == null ? default : JsonConvert.DeserializeObject<T>(Encoding.UTF8.GetString(res));
}
public string GetString(string cacheKey)
{
return _cache.GetString(cacheKey);
}
/// <summary>
/// 获取缓存
/// </summary>
/// <param name="cacheKey"></param>
/// <returns></returns>
public async Task<string> GetStringAsync(string cacheKey)
{
return await _cache.GetStringAsync(cacheKey);
}
public void Remove(string key)
{
_cache.Remove(key);
DelCacheKey(key);
}
/// <summary>
/// 删除缓存
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public async Task RemoveAsync(string key)
{
await _cache.RemoveAsync(key);
await DelCacheKeyAsync(key);
}
public void RemoveAll()
{
var catches = GetAllCacheKeys();
foreach (var @catch in catches) Remove(@catch);
catches.Clear();
_cache.SetString(CacheConst.KeyAll, JsonConvert.SerializeObject(catches));
}
public async Task RemoveAllAsync()
{
var catches = await GetAllCacheKeysAsync();
foreach (var @catch in catches) await RemoveAsync(@catch);
catches.Clear();
await _cache.SetStringAsync(CacheConst.KeyAll, JsonConvert.SerializeObject(catches));
}
public void Set<T>(string cacheKey, T value, TimeSpan? expire = null)
{
_cache.Set(cacheKey, GetBytes(value), expire == null ? new DistributedCacheEntryOptions() {AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(6)} : new DistributedCacheEntryOptions() {AbsoluteExpirationRelativeToNow = expire});
AddCacheKey(cacheKey);
}
/// <summary>
/// 增加对象缓存
/// </summary>
/// <param name="cacheKey"></param>
/// <param name="value"></param>
/// <returns></returns>
public async Task SetAsync<T>(string cacheKey, T value)
{
await _cache.SetAsync(cacheKey, Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(value)), new DistributedCacheEntryOptions() {AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(6)});
await AddCacheKeyAsync(cacheKey);
}
/// <summary>
/// 增加对象缓存,并设置过期时间
/// </summary>
/// <param name="cacheKey"></param>
/// <param name="value"></param>
/// <param name="expire"></param>
/// <returns></returns>
public async Task SetAsync<T>(string cacheKey, T value, TimeSpan expire)
{
await _cache.SetAsync(cacheKey, Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(value)), new DistributedCacheEntryOptions() {AbsoluteExpirationRelativeToNow = expire});
await AddCacheKeyAsync(cacheKey);
}
public void SetPermanent<T>(string cacheKey, T value)
{
_cache.Set(cacheKey, Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(value)));
AddCacheKey(cacheKey);
}
public async Task SetPermanentAsync<T>(string cacheKey, T value)
{
await _cache.SetAsync(cacheKey, Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(value)));
await AddCacheKeyAsync(cacheKey);
}
public void SetString(string cacheKey, string value, TimeSpan? expire = null)
{
if (expire == null)
_cache.SetString(cacheKey, value, new DistributedCacheEntryOptions() {AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(6)});
else
_cache.SetString(cacheKey, value, new DistributedCacheEntryOptions() {AbsoluteExpirationRelativeToNow = expire});
AddCacheKey(cacheKey);
}
/// <summary>
/// 增加字符串缓存
/// </summary>
/// <param name="cacheKey"></param>
/// <param name="value"></param>
/// <returns></returns>
public async Task SetStringAsync(string cacheKey, string value)
{
await _cache.SetStringAsync(cacheKey, value, new DistributedCacheEntryOptions() {AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(6)});
await AddCacheKeyAsync(cacheKey);
}
/// <summary>
/// 增加字符串缓存,并设置过期时间
/// </summary>
/// <param name="cacheKey"></param>
/// <param name="value"></param>
/// <param name="expire"></param>
/// <returns></returns>
public async Task SetStringAsync(string cacheKey, string value, TimeSpan expire)
{
await _cache.SetStringAsync(cacheKey, value, new DistributedCacheEntryOptions() {AbsoluteExpirationRelativeToNow = expire});
await AddCacheKeyAsync(cacheKey);
}
/// <summary>
/// 缓存最大角色数据范围
/// </summary>
/// <param name="userId"></param>
/// <param name="dataScopeType"></param>
/// <returns></returns>
public async Task SetMaxDataScopeType(long userId, int dataScopeType)
{
var cacheKey = CacheConst.KeyMaxDataScopeType + userId;
await SetStringAsync(cacheKey, dataScopeType.ToString());
await AddCacheKeyAsync(cacheKey);
}
/// <summary>
/// 根据父键清空
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public async Task DelByParentKeyAsync(string key)
{
var allkeys = await GetAllCacheKeysAsync();
if (allkeys == null) return;
var delAllkeys = allkeys.Where(u => u.StartsWith(key)).ToList();
delAllkeys.ForEach(Remove);
// 更新所有缓存键
allkeys = allkeys.Where(u => !u.StartsWith(key)).ToList();
await SetStringAsync(CacheConst.KeyAll, JsonConvert.SerializeObject(allkeys));
}
}

View File

@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Distributed;
namespace Blog.Core.Common.Caches;
/// <summary>
/// 缓存抽象接口,基于IDistributedCache封装
/// </summary>
public interface ICaching
{
public IDistributedCache Cache { get; }
void AddCacheKey(string cacheKey);
Task AddCacheKeyAsync(string cacheKey);
void DelByPattern(string key);
Task DelByPatternAsync(string key);
void DelCacheKey(string cacheKey);
Task DelCacheKeyAsync(string cacheKey);
bool Exists(string cacheKey);
Task<bool> ExistsAsync(string cacheKey);
List<string> GetAllCacheKeys();
Task<List<string>> GetAllCacheKeysAsync();
T Get<T>(string cacheKey);
Task<T> GetAsync<T>(string cacheKey);
string GetString(string cacheKey);
Task<string> GetStringAsync(string cacheKey);
void Remove(string key);
Task RemoveAsync(string key);
void RemoveAll();
Task RemoveAllAsync();
void Set<T>(string cacheKey, T value, TimeSpan? expire = null);
Task SetAsync<T>(string cacheKey, T value);
Task SetAsync<T>(string cacheKey, T value, TimeSpan expire);
void SetPermanent<T>(string cacheKey, T value);
Task SetPermanentAsync<T>(string cacheKey, T value);
void SetString(string cacheKey, string value, TimeSpan? expire = null);
Task SetStringAsync(string cacheKey, string value);
Task SetStringAsync(string cacheKey, string value, TimeSpan expire);
Task DelByParentKeyAsync(string key);
}

View File

@ -0,0 +1,62 @@
using System;
using System.Collections.Generic;
using SqlSugar;
namespace Blog.Core.Common.Caches;
/// <summary>
/// 实现SqlSugar的ICacheService接口
/// </summary>
public class SqlSugarCacheService : ICacheService
{
private readonly Lazy<ICaching> _caching = new(() => App.GetService<ICaching>(false));
private ICaching Caching => _caching.Value;
public void Add<V>(string key, V value)
{
Caching.Set(key, value);
}
public void Add<V>(string key, V value, int cacheDurationInSeconds)
{
Caching.Set(key, value, TimeSpan.FromSeconds(cacheDurationInSeconds));
}
public bool ContainsKey<V>(string key)
{
return Caching.Exists(key);
}
public V Get<V>(string key)
{
return Caching.Get<V>(key);
}
public IEnumerable<string> GetAllKey<V>()
{
return Caching.GetAllCacheKeys();
}
public V GetOrCreate<V>(string cacheKey, Func<V> create, int cacheDurationInSeconds = int.MaxValue)
{
if (!ContainsKey<V>(cacheKey))
{
var value = create();
Caching.Set(cacheKey, value, TimeSpan.FromSeconds(cacheDurationInSeconds));
return value;
}
return Caching.Get<V>(cacheKey);
}
public void Remove<V>(string key)
{
Caching.Remove(key);
}
public bool RemoveAll()
{
Caching.RemoveAll();
return true;
}
}

View File

@ -0,0 +1,87 @@
namespace Blog.Core.Common.Const;
/// <summary>
/// 缓存相关常量
/// </summary>
public class CacheConst
{
/// <summary>
/// 用户缓存
/// </summary>
public const string KeyUser = "user:";
/// <summary>
/// 用户部门缓存
/// </summary>
public const string KeyUserDepart = "userDepart:";
/// <summary>
/// 菜单缓存
/// </summary>
public const string KeyMenu = "menu:";
/// <summary>
/// 菜单
/// </summary>
public const string KeyPermissions = "permissions";
/// <summary>
/// 权限缓存
/// </summary>
public const string KeyPermission = "permission:";
/// <summary>
/// 接口路由
/// </summary>
public const string KeyModules = "modules";
/// <summary>
/// 系统配置
/// </summary>
public const string KeySystemConfig = "sysConfig";
/// <summary>
/// 查询过滤器缓存
/// </summary>
public const string KeyQueryFilter = "queryFilter:";
/// <summary>
/// 机构Id集合缓存
/// </summary>
public const string KeyOrgIdList = "org:";
/// <summary>
/// 最大角色数据范围缓存
/// </summary>
public const string KeyMaxDataScopeType = "maxDataScopeType:";
/// <summary>
/// 验证码缓存
/// </summary>
public const string KeyVerCode = "verCode:";
/// <summary>
/// 所有缓存关键字集合
/// </summary>
public const string KeyAll = "keys";
/// <summary>
/// 定时任务缓存
/// </summary>
public const string KeyTimer = "timer:";
/// <summary>
/// 在线用户缓存
/// </summary>
public const string KeyOnlineUser = "onlineuser:";
/// <summary>
/// 常量下拉框
/// </summary>
public const string KeyConstSelector = "selector:";
/// <summary>
/// swagger登录缓存
/// </summary>
public const string SwaggerLogin = "swaggerLogin:";
}

View File

@ -3,7 +3,6 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using SqlSugar;
namespace Blog.Core.Common.DB
{

View File

@ -4,211 +4,213 @@ using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using Blog.Core.Common.Caches;
namespace Blog.Core.Common.Helper
{
/// <summary>
/// Linq扩展
/// </summary>
public static class ExpressionExtensions
{
#region HttpContext
/// <summary>
/// Linq扩展
/// </summary>
public static class ExpressionExtensions
{
#region HttpContext
/// <summary>
/// 返回请求上下文
/// </summary>
/// <param name="context"></param>
/// <param name="code"></param>
/// <param name="message"></param>
/// <param name="ContentType"></param>
/// <returns></returns>
public static async Task Cof_SendResponse(this HttpContext context, System.Net.HttpStatusCode code, string message, string ContentType = "text/html;charset=utf-8")
{
context.Response.StatusCode = (int)code;
context.Response.ContentType = ContentType;
await context.Response.WriteAsync(message);
}
/// <summary>
/// 返回请求上下文
/// </summary>
/// <param name="context"></param>
/// <param name="code"></param>
/// <param name="message"></param>
/// <param name="ContentType"></param>
/// <returns></returns>
public static async Task Cof_SendResponse(this HttpContext context, System.Net.HttpStatusCode code, string message,
string ContentType = "text/html;charset=utf-8")
{
context.Response.StatusCode = (int) code;
context.Response.ContentType = ContentType;
await context.Response.WriteAsync(message);
}
#endregion
#endregion
#region ICaching
#region ICaching
/// <summary>
/// 从缓存里取数据,如果不存在则执行查询方法,
/// </summary>
/// <typeparam name="T">类型</typeparam>
/// <param name="cache">ICaching </param>
/// <param name="key">键值</param>
/// <param name="GetFun">查询方法</param>
/// <param name="timeSpanMin">有效期 单位分钟/param>
/// <returns></returns>
public static T Cof_GetICaching<T>(this ICaching cache, string key, Func<T> GetFun, int timeSpanMin) where T : class
{
var obj = cache.Get(key);
obj = GetFun();
if (obj == null)
{
obj = GetFun();
cache.Set(key, obj, timeSpanMin);
}
/// <summary>
/// 从缓存里取数据,如果不存在则执行查询方法,
/// </summary>
/// <typeparam name="T">类型</typeparam>
/// <param name="cache">ICaching </param>
/// <param name="key">键值</param>
/// <param name="GetFun">查询方法</param>
/// <param name="timeSpanMin">有效期 单位分钟/param>
/// <returns></returns>
public static T Cof_GetICaching<T>(this ICaching cache, string key, Func<T> GetFun, int timeSpanMin) where T : class
{
var obj = cache.Get<T>(key);
if (obj == null)
{
obj = GetFun();
cache.Set(key, obj, TimeSpan.FromMinutes(timeSpanMin));
}
return obj as T;
}
return obj;
}
/// <summary>
/// 异步从缓存里取数据,如果不存在则执行查询方法
/// </summary>
/// <typeparam name="T">类型</typeparam>
/// <param name="cache">ICaching </param>
/// <param name="key">键值</param>
/// <param name="GetFun">查询方法</param>
/// <param name="timeSpanMin">有效期 单位分钟/param>
/// <returns></returns>
public static async Task<T> Cof_AsyncGetICaching<T>(this ICaching cache, string key, Func<Task<T>> GetFun, int timeSpanMin) where T : class
{
var obj = cache.Get(key);
if (obj == null)
{
obj = await GetFun();
cache.Set(key, obj, timeSpanMin);
}
/// <summary>
/// 异步从缓存里取数据,如果不存在则执行查询方法
/// </summary>
/// <typeparam name="T">类型</typeparam>
/// <param name="cache">ICaching </param>
/// <param name="key">键值</param>
/// <param name="GetFun">查询方法</param>
/// <param name="timeSpanMin">有效期 单位分钟/param>
/// <returns></returns>
public static async Task<T> Cof_AsyncGetICaching<T>(this ICaching cache, string key, Func<Task<T>> GetFun, int timeSpanMin) where T : class
{
var obj = await cache.GetAsync<T>(key);
if (obj == null)
{
obj = await GetFun();
cache.Set(key, obj, TimeSpan.FromMinutes(timeSpanMin));
}
return obj as T;
}
return obj;
}
#endregion
#endregion
#region
#region
public static bool Cof_CheckAvailable<TSource>(this IEnumerable<TSource> Tlist)
{
return Tlist != null && Tlist.Count() > 0;
}
public static bool Cof_CheckAvailable<TSource>(this IEnumerable<TSource> Tlist)
{
return Tlist != null && Tlist.Count() > 0;
}
/// <summary>
/// 调用内部方法
/// </summary>
public static Expression Call(this Expression instance, string methodName, params Expression[] arguments)
{
if (instance.Type == typeof(string))
return Expression.Call(instance, instance.Type.GetMethod(methodName, new Type[] { typeof(string) }), arguments); //修复string contains 出现的问题 Ambiguous match found.
else
return Expression.Call(instance, instance.Type.GetMethod(methodName), arguments);
}
/// <summary>
/// 调用内部方法
/// </summary>
public static Expression Call(this Expression instance, string methodName, params Expression[] arguments)
{
if (instance.Type == typeof(string))
return Expression.Call(instance, instance.Type.GetMethod(methodName, new Type[] {typeof(string)}),
arguments); //修复string contains 出现的问题 Ambiguous match found.
else
return Expression.Call(instance, instance.Type.GetMethod(methodName), arguments);
}
/// <summary>
/// 获取内部成员
/// </summary>
public static Expression Property(this Expression expression, string propertyName)
{
// Todo:左边条件如果是dynamic
// 则Expression.Property无法获取子内容
// 报错在这里由于expression内的对象为Object所以无法解析到
// var x = (expression as IQueryable).ElementType;
var exp = Expression.Property(expression, propertyName);
if (exp.Type.IsGenericType && exp.Type.GetGenericTypeDefinition() == typeof(Nullable<>))
{
return Expression.Convert(exp, exp.Type.GetGenericArguments()[0]);
}
/// <summary>
/// 获取内部成员
/// </summary>
public static Expression Property(this Expression expression, string propertyName)
{
// Todo:左边条件如果是dynamic
// 则Expression.Property无法获取子内容
// 报错在这里由于expression内的对象为Object所以无法解析到
// var x = (expression as IQueryable).ElementType;
var exp = Expression.Property(expression, propertyName);
if (exp.Type.IsGenericType && exp.Type.GetGenericTypeDefinition() == typeof(Nullable<>))
{
return Expression.Convert(exp, exp.Type.GetGenericArguments()[0]);
}
return exp;
}
return exp;
}
/// <summary>
/// 转Lambda
/// </summary>
public static Expression<TDelegate> ToLambda<TDelegate>(this Expression body,
params ParameterExpression[] parameters)
{
return Expression.Lambda<TDelegate>(body, parameters);
}
/// <summary>
/// 转Lambda
/// </summary>
public static Expression<TDelegate> ToLambda<TDelegate>(this Expression body,
params ParameterExpression[] parameters)
{
return Expression.Lambda<TDelegate>(body, parameters);
}
#endregion
#endregion
#region [ > , >= , == , < , <= , != , || , && ]
#region [ > , >= , == , < , <= , != , || , && ]
/// <summary>
/// &&
/// </summary>
public static Expression AndAlso(this Expression left, Expression right)
{
return Expression.AndAlso(left, right);
}
/// <summary>
/// &&
/// </summary>
public static Expression AndAlso(this Expression left, Expression right)
{
return Expression.AndAlso(left, right);
}
/// <summary>
/// ||
/// </summary>
public static Expression OrElse(this Expression left, Expression right)
{
return Expression.OrElse(left, right);
}
/// <summary>
/// ||
/// </summary>
public static Expression OrElse(this Expression left, Expression right)
{
return Expression.OrElse(left, right);
}
/// <summary>
/// Contains
/// </summary>
public static Expression Contains(this Expression left, Expression right)
{
return left.Call("Contains", right);
}
/// <summary>
/// Contains
/// </summary>
public static Expression Contains(this Expression left, Expression right)
{
return left.Call("Contains", right);
}
public static Expression StartContains(this Expression left, Expression right)
{
return left.Call("StartsWith", right);
}
public static Expression StartContains(this Expression left, Expression right)
{
return left.Call("StartsWith", right);
}
public static Expression EndContains(this Expression left, Expression right)
{
return left.Call("EndsWith", right);
}
public static Expression EndContains(this Expression left, Expression right)
{
return left.Call("EndsWith", right);
}
/// <summary>
/// >
/// </summary>
public static Expression GreaterThan(this Expression left, Expression right)
{
return Expression.GreaterThan(left, right);
}
/// <summary>
/// >
/// </summary>
public static Expression GreaterThan(this Expression left, Expression right)
{
return Expression.GreaterThan(left, right);
}
/// <summary>
/// >=
/// </summary>
public static Expression GreaterThanOrEqual(this Expression left, Expression right)
{
return Expression.GreaterThanOrEqual(left, right);
}
/// <summary>
/// >=
/// </summary>
public static Expression GreaterThanOrEqual(this Expression left, Expression right)
{
return Expression.GreaterThanOrEqual(left, right);
}
/// <summary>
/// <
/// </summary>
public static Expression LessThan(this Expression left, Expression right)
{
return Expression.LessThan(left, right);
}
/// <summary>
/// <
/// </summary>
public static Expression LessThan(this Expression left, Expression right)
{
return Expression.LessThan(left, right);
}
/// <summary>
/// <=
/// </summary>
public static Expression LessThanOrEqual(this Expression left, Expression right)
{
return Expression.LessThanOrEqual(left, right);
}
/// <summary>
/// <=
/// </summary>
public static Expression LessThanOrEqual(this Expression left, Expression right)
{
return Expression.LessThanOrEqual(left, right);
}
/// <summary>
/// ==
/// </summary>
public static Expression Equal(this Expression left, Expression right)
{
return Expression.Equal(left, right);
}
/// <summary>
/// ==
/// </summary>
public static Expression Equal(this Expression left, Expression right)
{
return Expression.Equal(left, right);
}
/// <summary>
/// !=
/// </summary>
public static Expression NotEqual(this Expression left, Expression right)
{
return Expression.NotEqual(left, right);
}
/// <summary>
/// !=
/// </summary>
public static Expression NotEqual(this Expression left, Expression right)
{
return Expression.NotEqual(left, right);
}
#endregion
}
#endregion
}
}

View File

@ -1,12 +0,0 @@
namespace Blog.Core.Common
{
/// <summary>
/// 简单的缓存接口,只有查询和添加,以后会进行扩展
/// </summary>
public interface ICaching
{
object Get(string cacheKey);
void Set(string cacheKey, object cacheValue, int timeSpan);
}
}

View File

@ -1,30 +0,0 @@
using Microsoft.Extensions.Caching.Memory;
using System;
namespace Blog.Core.Common
{
/// <summary>
/// 实例化缓存接口ICaching
/// </summary>
public class MemoryCaching : ICaching
{
//引用Microsoft.Extensions.Caching.Memory;这个和.net 还是不一样没有了Httpruntime了
private readonly IMemoryCache _cache;
//还是通过构造函数的方法,获取
public MemoryCaching(IMemoryCache cache)
{
_cache = cache;
}
public object Get(string cacheKey)
{
return _cache.Get(cacheKey);
}
public void Set(string cacheKey, object cacheValue,int timeSpan)
{
_cache.Set(cacheKey, cacheValue, TimeSpan.FromSeconds(timeSpan * 60));
}
}
}

View File

@ -0,0 +1,24 @@
using Blog.Core.Common.Option.Core;
namespace Blog.Core.Common.Option;
/// <summary>
/// 缓存配置选项
/// </summary>
public sealed class RedisOptions : IConfigurableOptions
{
/// <summary>
/// 是否启用
/// </summary>
public bool Enable { get; set; }
/// <summary>
/// Redis连接
/// </summary>
public string ConnectionString { get; set; }
/// <summary>
/// 键值前缀
/// </summary>
public string InstanceName { get; set; }
}

View File

@ -1,6 +1,8 @@
using Blog.Core.Common;
using System;
using Blog.Core.Common;
using Castle.DynamicProxy;
using System.Linq;
using Blog.Core.Common.Caches;
namespace Blog.Core.AOP
{
@ -28,7 +30,7 @@ namespace Blog.Core.AOP
//获取自定义缓存键
var cacheKey = CustomCacheKey(invocation);
//根据key获取相应的缓存值
var cacheValue = _cache.Get(cacheKey);
var cacheValue = _cache.Get<object>(cacheKey);
if (cacheValue != null)
{
//将当前获取到的缓存值,赋值给当前执行方法
@ -40,7 +42,7 @@ namespace Blog.Core.AOP
//存入缓存
if (!string.IsNullOrWhiteSpace(cacheKey))
{
_cache.Set(cacheKey, invocation.ReturnValue, qCachingAttribute.AbsoluteExpiration);
_cache.Set(cacheKey, invocation.ReturnValue, TimeSpan.FromMinutes(qCachingAttribute.AbsoluteExpiration));
}
}
else

View File

@ -15,6 +15,7 @@
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="6.0.8" />
<PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.1.0" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" Version="6.0.8" />
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="7.0.5" />
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="6.0.8" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="6.0.0" />

View File

@ -8,6 +8,7 @@ namespace Blog.Core.Extensions
/// <summary>
/// Redis缓存接口
/// </summary>
[Obsolete("普通缓存考虑直接使用ICaching,如果要使用Redis队列等还是使用此类")]
public interface IRedisBasketRepository
{

View File

@ -9,6 +9,7 @@ using System.Threading.Tasks;
namespace Blog.Core.Extensions
{
[Obsolete("普通缓存考虑直接使用ICaching,如果要使用Redis队列等还是使用此类")]
public class RedisBasketRepository : IRedisBasketRepository
{
private readonly ILogger<RedisBasketRepository> _logger;

View File

@ -0,0 +1,45 @@
using Blog.Core.Common;
using Blog.Core.Common.Caches;
using Blog.Core.Common.Option;
using Microsoft.Extensions.DependencyInjection;
using StackExchange.Redis;
namespace Blog.Core.Extensions.ServiceExtensions;
public static class CacheSetup
{
/// <summary>
/// 统一注册缓存
/// </summary>
/// <param name="services"></param>
public static void AddCacheSetup(this IServiceCollection services)
{
var cacheOptions = App.GetOptions<RedisOptions>();
if (cacheOptions.Enable)
{
//使用Redis
services.AddStackExchangeRedisCache(options =>
{
options.Configuration = cacheOptions.ConnectionString;
if (!cacheOptions.InstanceName.IsNullOrEmpty()) options.InstanceName = cacheOptions.InstanceName;
});
services.AddTransient<IRedisBasketRepository, RedisBasketRepository>();
// 配置启动Redis服务虽然可能影响项目启动速度但是不能在运行的时候报错所以是合理的
services.AddSingleton<ConnectionMultiplexer>(sp =>
{
//获取连接字符串
var configuration = ConfigurationOptions.Parse(cacheOptions.ConnectionString, true);
configuration.ResolveDns = true;
return ConnectionMultiplexer.Connect(configuration);
});
}
else
{
//使用内存
services.AddDistributedMemoryCache();
}
services.AddSingleton<ICaching, Caching>();
}
}

View File

@ -1,67 +0,0 @@
using Microsoft.Extensions.Caching.Memory;
using SqlSugar;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
namespace Blog.Core.Extensions
{
/// <summary>
/// 实现SqlSugar的ICacheService接口
/// </summary>
public class SqlSugarMemoryCacheService : ICacheService
{
protected IMemoryCache _memoryCache;
public SqlSugarMemoryCacheService(IMemoryCache memoryCache)
{
_memoryCache = memoryCache;
}
public void Add<V>(string key, V value)
{
_memoryCache.Set(key, value);
}
public void Add<V>(string key, V value, int cacheDurationInSeconds)
{
_memoryCache.Set(key, value, DateTimeOffset.Now.AddSeconds(cacheDurationInSeconds));
}
public bool ContainsKey<V>(string key)
{
return _memoryCache.TryGetValue(key, out _);
}
public V Get<V>(string key)
{
return _memoryCache.Get<V>(key);
}
public IEnumerable<string> GetAllKey<V>()
{
const BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic;
var entries = _memoryCache.GetType().GetField("_entries", flags).GetValue(_memoryCache);
var cacheItems = entries as IDictionary;
var keys = new List<string>();
if (cacheItems == null) return keys;
foreach (DictionaryEntry cacheItem in cacheItems)
{
keys.Add(cacheItem.Key.ToString());
}
return keys;
}
public V GetOrCreate<V>(string cacheKey, Func<V> create, int cacheDurationInSeconds = int.MaxValue)
{
if (!_memoryCache.TryGetValue<V>(cacheKey, out V value))
{
value = create();
_memoryCache.Set(cacheKey, value, DateTime.Now.AddSeconds(cacheDurationInSeconds));
}
return value;
}
public void Remove<V>(string key)
{
_memoryCache.Remove(key);
}
}
}

View File

@ -1,27 +0,0 @@
using Blog.Core.Common;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using System;
namespace Blog.Core.Extensions
{
/// <summary>
/// Memory缓存 启动服务
/// </summary>
public static class MemoryCacheSetup
{
public static void AddMemoryCacheSetup(this IServiceCollection services)
{
if (services == null) throw new ArgumentNullException(nameof(services));
services.AddScoped<ICaching, MemoryCaching>();
services.AddSingleton<IMemoryCache>(factory =>
{
var value = factory.GetRequiredService<IOptions<MemoryCacheOptions>>();
var cache = new MemoryCache(value);
return cache;
});
}
}
}

View File

@ -1,34 +0,0 @@
using Blog.Core.Common;
using Microsoft.Extensions.DependencyInjection;
using StackExchange.Redis;
using System;
namespace Blog.Core.Extensions
{
/// <summary>
/// Redis缓存 启动服务
/// </summary>
public static class RedisCacheSetup
{
public static void AddRedisCacheSetup(this IServiceCollection services)
{
if (services == null) throw new ArgumentNullException(nameof(services));
services.AddTransient<IRedisBasketRepository, RedisBasketRepository>();
// 配置启动Redis服务虽然可能影响项目启动速度但是不能在运行的时候报错所以是合理的
services.AddSingleton<ConnectionMultiplexer>(sp =>
{
//获取连接字符串
string redisConfiguration = AppSettings.app(new string[] { "Redis", "ConnectionString" });
var configuration = ConfigurationOptions.Parse(redisConfiguration, true);
configuration.ResolveDns = true;
return ConnectionMultiplexer.Connect(configuration);
});
}
}
}

View File

@ -10,6 +10,7 @@ using StackExchange.Profiling;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Blog.Core.Common.Caches;
namespace Blog.Core.Extensions
{
@ -57,6 +58,7 @@ namespace Blog.Core.Extensions
// 自定义特性
ConfigureExternalServices = new ConfigureExternalServices()
{
DataInfoCacheService = new SqlSugarCacheService(),
EntityService = (property, column) =>
{
if (column.IsPrimarykey && property.PropertyType == typeof(int))
@ -83,19 +85,11 @@ namespace Blog.Core.Extensions
{
throw new ApplicationException("未配置Log库连接");
}
// SqlSugarScope是线程安全可使用单例注入
// 参考https://www.donet5.com/Home/Doc?typeId=1181
services.AddSingleton<ISqlSugarClient>(o =>
{
var memoryCache = o.GetRequiredService<IMemoryCache>();
foreach (var config in BaseDBConfig.AllConfigs)
{
config.ConfigureExternalServices.DataInfoCacheService = new SqlSugarMemoryCacheService(memoryCache);
}
return new SqlSugarScope(BaseDBConfig.AllConfigs, db =>
{
BaseDBConfig.ValidConfig.ForEach(config =>

View File

@ -7,6 +7,7 @@ using System.Text.RegularExpressions;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Blog.Core.Common;
using Blog.Core.Common.Caches;
using Blog.Core.Common.Helper;
using Nacos.V2;
using Newtonsoft.Json.Linq;

View File

@ -8,64 +8,66 @@ namespace Blog.Core.Serilog.Utility;
public class SerilogRequestUtility
{
public const string HttpMessageTemplate =
"HTTP {RequestMethod} {RequestPath} QueryString:{QueryString} Body:{Body} responded {StatusCode} in {Elapsed:0.0000} ms";
public const string HttpMessageTemplate =
"HTTP {RequestMethod} {RequestPath} QueryString:{QueryString} Body:{Body} responded {StatusCode} in {Elapsed:0.0000} ms";
private static readonly List<string> _ignoreUrl = new()
{
"/job",
};
private static readonly List<string> _ignoreUrl = new()
{
"/job",
};
private static LogEventLevel DefaultGetLevel(HttpContext ctx,
double _,
Exception? ex)
{
return ex is null && ctx.Response.StatusCode <= 499 ? LogEventLevel.Information : LogEventLevel.Error;
}
private static LogEventLevel DefaultGetLevel(HttpContext ctx,
double _,
Exception? ex)
{
return ex is null && ctx.Response.StatusCode <= 499 ? LogEventLevel.Information : LogEventLevel.Error;
}
public static LogEventLevel GetRequestLevel(HttpContext ctx, double _, Exception? ex) =>
ex is null && ctx.Response.StatusCode <= 499 ? IgnoreRequest(ctx) : LogEventLevel.Error;
public static LogEventLevel GetRequestLevel(HttpContext ctx, double _, Exception? ex) =>
ex is null && ctx.Response.StatusCode <= 499 ? IgnoreRequest(ctx) : LogEventLevel.Error;
private static LogEventLevel IgnoreRequest(HttpContext ctx)
{
var path = ctx.Request.Path.Value;
if (path.IsNullOrEmpty())
{
return LogEventLevel.Information;
}
private static LogEventLevel IgnoreRequest(HttpContext ctx)
{
var path = ctx.Request.Path.Value;
if (path.IsNullOrEmpty())
{
return LogEventLevel.Information;
}
return _ignoreUrl.Any(s => path.StartsWith(s)) ? LogEventLevel.Verbose : LogEventLevel.Information;
}
return _ignoreUrl.Any(s => path.StartsWith(s)) ? LogEventLevel.Verbose : LogEventLevel.Information;
}
/// <summary>
/// 从Request中增加附属属性
/// </summary>
/// <param name="diagnosticContext"></param>
/// <param name="httpContext"></param>
public static void EnrichFromRequest(IDiagnosticContext diagnosticContext, HttpContext httpContext)
{
var request = httpContext.Request;
/// <summary>
/// 从Request中增加附属属性
/// </summary>
/// <param name="diagnosticContext"></param>
/// <param name="httpContext"></param>
public static void EnrichFromRequest(IDiagnosticContext diagnosticContext, HttpContext httpContext)
{
var request = httpContext.Request;
diagnosticContext.Set("RequestHost", request.Host);
diagnosticContext.Set("RequestScheme", request.Scheme);
diagnosticContext.Set("Protocol", request.Protocol);
diagnosticContext.Set("RequestIp", httpContext.GetRequestIp());
diagnosticContext.Set("RequestHost", request.Host);
diagnosticContext.Set("RequestScheme", request.Scheme);
diagnosticContext.Set("Protocol", request.Protocol);
diagnosticContext.Set("RequestIp", httpContext.GetRequestIp());
if (request.Method == HttpMethods.Get)
{
diagnosticContext.Set("QueryString", request.QueryString.HasValue ? request.QueryString.Value : string.Empty);
}
else
{
diagnosticContext.Set("QueryString", request.QueryString.HasValue ? request.QueryString.Value : string.Empty);
diagnosticContext.Set("Body", request.ContentLength > 0 ? request.GetRequestBody() : string.Empty);
}
diagnosticContext.Set("ContentType", httpContext.Response.ContentType);
if (request.Method == HttpMethods.Get)
{
diagnosticContext.Set("QueryString", request.QueryString.HasValue ? request.QueryString.Value : string.Empty);
diagnosticContext.Set("Body", string.Empty);
}
else
{
diagnosticContext.Set("QueryString", request.QueryString.HasValue ? request.QueryString.Value : string.Empty);
diagnosticContext.Set("Body", request.ContentLength > 0 ? request.GetRequestBody() : string.Empty);
}
var endpoint = httpContext.GetEndpoint();
if (endpoint != null)
{
diagnosticContext.Set("EndpointName", endpoint.DisplayName);
}
}
diagnosticContext.Set("ContentType", httpContext.Response.ContentType);
var endpoint = httpContext.GetEndpoint();
if (endpoint != null)
{
diagnosticContext.Set("EndpointName", endpoint.DisplayName);
}
}
}