ace缓存扩展接口_基于 abp vNext 和 .NET Core 开发博客项目 使用Redis缓存数据

上一篇文章完成了项目的全局异常处理和日志记录。

在日志记录中使用的静态方法有人指出写法不是很优雅,遂优化一下上一篇中日志记录的方法,具体操作如下:

.ToolKits层中新建扩展方法Log4NetExtensions.cs

//Log4NetExtensions.csusing log4net;using log4net.Config;using Microsoft.Extensions.Hosting;using System.IO;using System.Reflection;namespace Meowv.Blog.ToolKits.Extensions{    public static class Log4NetExtensions    {        public static IHostBuilder UseLog4Net(this IHostBuilder hostBuilder)        {            var log4netRepository = LogManager.GetRepository(Assembly.GetEntryAssembly());            XmlConfigurator.Configure(log4netRepository, new FileInfo("log4net.config"));            return hostBuilder;        }    }}

配置log4net,然后我们直接返回IHostBuilder对象,便于在Main方法中链式调用。

//Program.csusing Meowv.Blog.ToolKits.Extensions;using Microsoft.AspNetCore.Hosting;using Microsoft.Extensions.Hosting;using System.Threading.Tasks;namespace Meowv.Blog.HttpApi.Hosting{    public class Program    {        public static async Task Main(string[] args)        {            await Host.CreateDefaultBuilder(args)                      .UseLog4Net()                      .ConfigureWebHostDefaults(builder =>                      {                          builder.UseIISIntegration()                                 .UseStartup();                      }).UseAutofac().Build().RunAsync();        }    }}

然后修改MeowvBlogExceptionFilter过滤器,代码如下:

//MeowvBlogExceptionFilter.csusing log4net;using Microsoft.AspNetCore.Mvc.Filters;namespace Meowv.Blog.HttpApi.Hosting.Filters{    public class MeowvBlogExceptionFilter : IExceptionFilter    {        private readonly ILog _log;        public MeowvBlogExceptionFilter()        {            _log = LogManager.GetLogger(typeof(MeowvBlogExceptionFilter));        }        ///         /// 异常处理        ///         ///         ///         public void OnException(ExceptionContext context)        {            // 错误日志记录            _log.Error($"{context.HttpContext.Request.Path}|{context.Exception.Message}", context.Exception);        }    }}

可以删掉之前添加的LoggerHelper.cs类,运行一下,同样可以达到预期效果。


本篇将集成Redis,使用Redis来缓存数据,使用方法参考的微软官方文档:https://docs.microsoft.com/zh-cn/aspnet/core/performance/caching/distributed

关于Redis的介绍这里就不多说了,这里有一篇快速入门的文章:Redis快速入门及使用,对于不了解的同学可以看看。

直入主题,先在appsettings.json配置Redis的连接字符串。

//appsettings.json...  "Caching": {    "RedisConnectionString": "127.0.0.1:6379,password=123456,ConnectTimeout=15000,SyncTimeout=5000"  }...

对应的,在AppSettings.cs中读取。

//AppSettings.cs...        ///         /// Caching        ///         public static class Caching        {            ///             /// RedisConnectionString            ///             public static string RedisConnectionString => _config["Caching:RedisConnectionString"];        }...

.Application.Caching层添加包Microsoft.Extensions.Caching.StackExchangeRedis,然后在模块类MeowvBlogApplicationCachingModule中添加配置缓存实现。

//MeowvBlogApplicationCachingModule.csusing Meowv.Blog.Domain;using Meowv.Blog.Domain.Configurations;using Microsoft.Extensions.DependencyInjection;using Volo.Abp.Caching;using Volo.Abp.Modularity;namespace Meowv.Blog.Application.Caching{    [DependsOn(        typeof(AbpCachingModule),        typeof(MeowvBlogDomainModule)    )]    public class MeowvBlogApplicationCachingModule : AbpModule    {        public override void ConfigureServices(ServiceConfigurationContext context)        {            context.Services.AddStackExchangeRedisCache(options =>            {                options.Configuration = AppSettings.Caching.RedisConnectionString;                //options.InstanceName                //options.ConfigurationOptions            });        }    }}

options.Configuration是 Redis 的连接字符串。

options.InstanceNam是 Redis 实例名称,这里没填。

options.ConfigurationOptions是 Redis 的配置属性,如果配置了这个字,将优先于 Configuration 中的配置,同时它支持更多的选项。我这里也没填。

紧接着我们就可以直接使用了,直接将IDistributedCache接口依赖关系注入即可。

f1826d9755a5ba73ccc3a7a5bdf16f86.png

可以看到默认已经实现了这么多常用的接口,已经够我这个小项目用的了,同时在Microsoft.Extensions.Caching.Distributed.DistributedCacheExtensions中微软还给我们提供了很多扩展方法。

于是,我们我就想到写一个新的扩展方法,可以同时处理获取和添加缓存的操作,当缓存存在时,直接返回,不存在时,添加缓存。

新建MeowvBlogApplicationCachingExtensions.cs扩展方法,如下:

//MeowvBlogApplicationCachingExtensions.csusing Meowv.Blog.ToolKits.Extensions;using Microsoft.Extensions.Caching.Distributed;using System;using System.Threading.Tasks;namespace Meowv.Blog.Application.Caching{    public static class MeowvBlogApplicationCachingExtensions    {        ///         /// 获取或添加缓存        ///         ///         ///         ///         ///         ///         ///         public static async Task GetOrAddAsync(this IDistributedCache cache, string key, Func> factory, int minutes)        {            TCacheItem cacheItem;            var result = await cache.GetStringAsync(key);            if (string.IsNullOrEmpty(result))            {                cacheItem = await factory.Invoke();                var options = new DistributedCacheEntryOptions();                if (minutes != CacheStrategy.NEVER)                {                    options.AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(minutes);                }                await cache.SetStringAsync(key, cacheItem.ToJson(), options);            }            else            {                cacheItem = result.FromJson();            }            return cacheItem;        }    }}

我们可以在DistributedCacheEntryOptions中可以配置我们的缓存过期时间,其中有一个判断条件,就是当minutes = -1的时候,不指定过期时间,那么我们的缓存就不会过期了。

GetStringAsync()SetStringAsync()DistributedCacheExtensions的扩展方法,最终会将缓存项cacheItem转换成JSON格式进行存储。

CacheStrategy是在.Domain.Shared层定义的缓存过期时间策略常量。

//MeowvBlogConsts.cs...        ///         /// 缓存过期时间策略        ///         public static class CacheStrategy        {            ///             /// 一天过期24小时            ///             public const int ONE_DAY = 1440;            ///             /// 12小时过期            ///             public const int HALF_DAY = 720;            ///             /// 8小时过期            ///             public const int EIGHT_HOURS = 480;            ///             /// 5小时过期            ///             public const int FIVE_HOURS = 300;            ///             /// 3小时过期            ///             public const int THREE_HOURS = 180;            ///             /// 2小时过期            ///             public const int TWO_HOURS = 120;            ///             /// 1小时过期            ///             public const int ONE_HOURS = 60;            ///             /// 半小时过期            ///             public const int HALF_HOURS = 30;            ///             /// 5分钟过期            ///             public const int FIVE_MINUTES = 5;            ///             /// 1分钟过期            ///             public const int ONE_MINUTE = 1;            ///             /// 永不过期            ///             public const int NEVER = -1;        }...

接下来去创建缓存接口类和实现类,然后再我们的引用服务层.Application中进行调用,拿上一篇中接入GitHub的几个接口来做新增缓存操作。

.Application层格式一样,在.Application.Caching中新建Authorize文件夹,添加缓存接口IAuthorizeCacheService和实现类AuthorizeCacheService

注意命名规范,实现类肯定要继承一个公共的CachingServiceBase基类。在.Application.Caching层根目录添加MeowvBlogApplicationCachingServiceBase.cs,继承ITransientDependency

//MeowvBlogApplicationCachingServiceBase.csusing Microsoft.Extensions.Caching.Distributed;using Volo.Abp.DependencyInjection;namespace Meowv.Blog.Application.Caching{    public class CachingServiceBase : ITransientDependency    {        public IDistributedCache Cache { get; set; }    }}

然后使用属性注入的方式,注入IDistributedCache。这样我们只要继承了基类:CachingServiceBase,就可以愉快的使用缓存了。

添加要缓存的接口到IAuthorizeCacheService,在这里我们使用Func()方法,我们的接口返回什么类型由Func()来决定,于是添加三个接口如下:

//IAuthorizeCacheService.csusing Meowv.Blog.ToolKits.Base;using System;using System.Threading.Tasks;namespace Meowv.Blog.Application.Caching.Authorize{    public interface IAuthorizeCacheService    {        ///         /// 获取登录地址(GitHub)        ///         ///         Taskstring>> GetLoginAddressAsync(Func        ///         /// 获取AccessToken        ///         ///         ///         ///         Taskstring>> GetAccessTokenAsync(        ///         /// 登录成功,生成Token        ///         ///         ///         ///         Taskstring>> GenerateTokenAsync(    }}

是不是和IAuthorizeService代码很像,的确,我就是直接复制过来改的。

AuthorizeCacheService中实现接口。

//AuthorizeCacheService.csusing Meowv.Blog.ToolKits.Base;using Meowv.Blog.ToolKits.Extensions;using System;using System.Threading.Tasks;using static Meowv.Blog.Domain.Shared.MeowvBlogConsts;namespace Meowv.Blog.Application.Caching.Authorize.Impl{    public class AuthorizeCacheService : CachingServiceBase, IAuthorizeCacheService    {        private const string KEY_GetLoginAddress = "Authorize:GetLoginAddress";        private const string KEY_GetAccessToken = "Authorize:GetAccessToken-{0}";        private const string KEY_GenerateToken = "Authorize:GenerateToken-{0}";        ///         /// 获取登录地址(GitHub)        ///         ///         ///         public async Taskstring>> GetLoginAddressAsync(Func        {            return await Cache.GetOrAddAsync(KEY_GetLoginAddress, factory, CacheStrategy.NEVER);        }        ///         /// 获取AccessToken        ///         ///         ///         ///         public async Taskstring>> GetAccessTokenAsync(        {            return await Cache.GetOrAddAsync(KEY_GetAccessToken.FormatWith(code), factory, CacheStrategy.FIVE_MINUTES);        }        ///         /// 登录成功,生成Token        ///         ///         ///         ///         public async Taskstring>> GenerateTokenAsync(        {            return await Cache.GetOrAddAsync(KEY_GenerateToken.FormatWith(access_token), factory, CacheStrategy.ONE_HOURS);        }    }}

代码很简单,每个缓存都有固定KEY值,根据参数生成KEY,然后调用前面写的扩展方法,再给一个过期时间即可,可以看到KEY里面包含了冒号 :,这个冒号 : 可以起到类似于文件夹的操作,在界面化管理工具中可以很友好的查看。

这样我们的缓存就搞定了,然后在.Application层对应的Service中进行调用。代码如下:

//AuthorizeService.csusing Meowv.Blog.Application.Caching.Authorize;using Meowv.Blog.Domain.Configurations;using Meowv.Blog.ToolKits.Base;using Meowv.Blog.ToolKits.Extensions;using Meowv.Blog.ToolKits.GitHub;using Microsoft.IdentityModel.Tokens;using System;using System.IdentityModel.Tokens.Jwt;using System.Linq;using System.Net;using System.Net.Http;using System.Net.Http.Headers;using System.Security.Claims;using System.Threading.Tasks;namespace Meowv.Blog.Application.Authorize.Impl{    public class AuthorizeService : ServiceBase, IAuthorizeService    {        private readonly IAuthorizeCacheService _authorizeCacheService;        private readonly IHttpClientFactory _httpClient;        public AuthorizeService(IAuthorizeCacheService authorizeCacheService,                                IHttpClientFactory httpClient)        {            _authorizeCacheService = authorizeCacheService;            _httpClient = httpClient;        }        ///         /// 获取登录地址(GitHub)        ///         ///         public async Taskstring>> GetLoginAddressAsync()        {            return await _authorizeCacheService.GetLoginAddressAsync(async () =>            {                var result = new ServiceResult<string>();                var request = new AuthorizeRequest();                var address = string.Concat(new string[]                {                    GitHubConfig.API_Authorize,                    "?client_id=", request.Client_ID,                    "&scope=", request.Scope,                    "&state=", request.State,                    "&redirect_uri=", request.Redirect_Uri                });                result.IsSuccess(address);                return await Task.FromResult(result);            });        }        ///         /// 获取AccessToken        ///         ///         ///         public async Taskstring>> GetAccessTokenAsync(        {            var result = new ServiceResult<string>();            if (string.IsNullOrEmpty(code))            {                result.IsFailed("code为空");                return result;            }            return await _authorizeCacheService.GetAccessTokenAsync(code, async () =>            {                var request = new AccessTokenRequest();                var content = new StringContent($"code={code}&client_id={request.Client_ID}&redirect_uri={request.Redirect_Uri}&client_secret={request.Client_Secret}");                content.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded");                using var client = _httpClient.CreateClient();                var httpResponse = await client.PostAsync(GitHubConfig.API_AccessToken, content);                var response = await httpResponse.Content.ReadAsStringAsync();                if (response.StartsWith("access_token"))                    result.IsSuccess(response.Split("=")[1].Split("&").First());                else                    result.IsFailed("code不正确");                return result;            });        }        ///         /// 登录成功,生成Token        ///         ///         ///         public async Taskstring>> GenerateTokenAsync(        {            var result = new ServiceResult<string>();            if (string.IsNullOrEmpty(access_token))            {                result.IsFailed("access_token为空");                return result;            }            return await _authorizeCacheService.GenerateTokenAsync(access_token, async () =>            {                var url = $"{GitHubConfig.API_User}?access_token={access_token}";                using var client = _httpClient.CreateClient();                client.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.14 Safari/537.36 Edg/83.0.478.13");                var httpResponse = await client.GetAsync(url);                if (httpResponse.StatusCode != HttpStatusCode.OK)                {                    result.IsFailed("access_token不正确");                    return result;                }                var content = await httpResponse.Content.ReadAsStringAsync();                var user = content.FromJson();                if (user.IsNull())                {                    result.IsFailed("未获取到用户数据");                    return result;                }                if (user.Id != GitHubConfig.UserId)                {                    result.IsFailed("当前账号未授权");                    return result;                }                var claims = new[] {                    new Claim(ClaimTypes.Name, user.Name),                    new Claim(ClaimTypes.Email, user.Email),                    new Claim(JwtRegisteredClaimNames.Exp, $"{new DateTimeOffset(DateTime.Now.AddMinutes(AppSettings.JWT.Expires)).ToUnixTimeSeconds()}"),                    new Claim(JwtRegisteredClaimNames.Nbf, $"{new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds()}")                };                var key = new SymmetricSecurityKey(AppSettings.JWT.SecurityKey.SerializeUtf8());                var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);                var securityToken = new JwtSecurityToken(                    issuer: AppSettings.JWT.Domain,                    audience: AppSettings.JWT.Domain,                    claims: claims,                    expires: DateTime.Now.AddMinutes(AppSettings.JWT.Expires),                    signingCredentials: creds);                var token = new JwtSecurityTokenHandler().WriteToken(securityToken);                result.IsSuccess(token);                return await Task.FromResult(result);            });        }    }}

直接return我们的缓存接口,当查询到Redis中存在KEY值的缓存就不会再走我们的具体的实现方法了。

注意注意,千万不要忘了在.Application层的模块类中添加依赖缓存模块MeowvBlogApplicationCachingModule,不然就会报错报错报错(我就是忘了添加...)

//MeowvBlogApplicationCachingModule.csusing Meowv.Blog.Domain;using Meowv.Blog.Domain.Configurations;using Microsoft.Extensions.DependencyInjection;using Volo.Abp.Caching;using Volo.Abp.Modularity;namespace Meowv.Blog.Application.Caching{    [DependsOn(        typeof(AbpCachingModule),        typeof(MeowvBlogDomainModule)    )]    public class MeowvBlogApplicationCachingModule : AbpModule    {        public override void ConfigureServices(ServiceConfigurationContext context)        {            context.Services.AddStackExchangeRedisCache(options =>            {                options.Configuration = AppSettings.Caching.RedisConnectionString;            });        }    }}

此时项目的层级目录结构。

cd973a460088fd0699a5cdb95e663f7a.png

好的,编译运行项目,现在去调用接口看看效果,为了真实,这里我先将我redis缓存数据全部干掉。

5e116830934d6ab86db40d01a3627ff0.png

访问接口,.../auth/url,成功返回数据,现在再去看看我们的redis。

39a8e7079caa2bbd5a013bd7c1ed4dad.png

成功将KEY为:Authorize:GetLoginAddress 添加进去了,这里直接使用RedisDesktopManager进行查看。

6e92d68785c9a0e9367793c8dd16f5df.png

那么再次调用这个接口,只要没有过期,就会直接返回数据了,调试图如下:

da30480552eeb56e5499e5db9079a69c.png

可以看到,是可以直接取到缓存数据的,其他接口大家自己试试吧,一样的效果。

是不是很简单,用最少的代码集成Redis进行数据缓存,你学会了吗????

开源地址:https://github.com/Meowv/Blog/tree/blog_tutorial

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值