基于 abp vNext 和 .NET Core 开发博客项目 - 使用Redis缓存数据

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

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

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

//Log4NetExtensions.cs
using 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.cs
using 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<Startup>();}).UseAutofac().Build().RunAsync();}}
}

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

//MeowvBlogExceptionFilter.cs
using 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));}/// <summary>/// 异常处理/// </summary>/// <param name="context"></param>/// <returns></returns>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
.../// <summary>/// Caching/// </summary>public static class Caching{/// <summary>/// RedisConnectionString/// </summary>public static string RedisConnectionString => _config["Caching:RedisConnectionString"];}
...

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

//MeowvBlogApplicationCachingModule.cs
using 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接口依赖关系注入即可。

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

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

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

//MeowvBlogApplicationCachingExtensions.cs
using Meowv.Blog.ToolKits.Extensions;
using Microsoft.Extensions.Caching.Distributed;
using System;
using System.Threading.Tasks;namespace Meowv.Blog.Application.Caching
{public static class MeowvBlogApplicationCachingExtensions{/// <summary>/// 获取或添加缓存/// </summary>/// <typeparam name="TCacheItem"></typeparam>/// <param name="cache"></param>/// <param name="key"></param>/// <param name="factory"></param>/// <param name="minutes"></param>/// <returns></returns>public static async Task<TCacheItem> GetOrAddAsync<TCacheItem>(this IDistributedCache cache, string key, Func<Task<TCacheItem>> 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<TCacheItem>();}return cacheItem;}}
}

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

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

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

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

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

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

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

//MeowvBlogApplicationCachingServiceBase.cs
using 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.cs
using Meowv.Blog.ToolKits.Base;
using System;
using System.Threading.Tasks;namespace Meowv.Blog.Application.Caching.Authorize
{public interface IAuthorizeCacheService{/// <summary>/// 获取登录地址(GitHub)/// </summary>/// <returns></returns>Task<ServiceResult<string>> GetLoginAddressAsync(Func<Task<ServiceResult<string>>> factory);/// <summary>/// 获取AccessToken/// </summary>/// <param name="code"></param>/// <param name="factory"></param>/// <returns></returns>Task<ServiceResult<string>> GetAccessTokenAsync(string code, Func<Task<ServiceResult<string>>> factory);/// <summary>/// 登录成功,生成Token/// </summary>/// <param name="access_token"></param>/// <param name="factory"></param>/// <returns></returns>Task<ServiceResult<string>> GenerateTokenAsync(string access_token, Func<Task<ServiceResult<string>>> factory);}
}

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

AuthorizeCacheService中实现接口。

//AuthorizeCacheService.cs
using 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}";/// <summary>/// 获取登录地址(GitHub)/// </summary>/// <param name="factory"></param>/// <returns></returns>public async Task<ServiceResult<string>> GetLoginAddressAsync(Func<Task<ServiceResult<string>>> factory){return await Cache.GetOrAddAsync(KEY_GetLoginAddress, factory, CacheStrategy.NEVER);}/// <summary>/// 获取AccessToken/// </summary>/// <param name="code"></param>/// <param name="factory"></param>/// <returns></returns>public async Task<ServiceResult<string>> GetAccessTokenAsync(string code, Func<Task<ServiceResult<string>>> factory){return await Cache.GetOrAddAsync(KEY_GetAccessToken.FormatWith(code), factory, CacheStrategy.FIVE_MINUTES);}/// <summary>/// 登录成功,生成Token/// </summary>/// <param name="access_token"></param>/// <param name="factory"></param>/// <returns></returns>public async Task<ServiceResult<string>> GenerateTokenAsync(string access_token, Func<Task<ServiceResult<string>>> factory){return await Cache.GetOrAddAsync(KEY_GenerateToken.FormatWith(access_token), factory, CacheStrategy.ONE_HOURS);}}
}

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

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

//AuthorizeService.cs
using 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;}/// <summary>/// 获取登录地址(GitHub)/// </summary>/// <returns></returns>public async Task<ServiceResult<string>> 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);});}/// <summary>/// 获取AccessToken/// </summary>/// <param name="code"></param>/// <returns></returns>public async Task<ServiceResult<string>> GetAccessTokenAsync(string code){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());elseresult.IsFailed("code不正确");return result;});}/// <summary>/// 登录成功,生成Token/// </summary>/// <param name="access_token"></param>/// <returns></returns>public async Task<ServiceResult<string>> GenerateTokenAsync(string access_token){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<UserResponse>();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.cs
using 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;});}}
}

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

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

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

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

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

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

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/309630.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

第一讲 工作区和GOPATH

此为 《极客时间&Go语言核心36讲》 个人笔记&#xff0c;具体课程详见极客时间官网。 Table of Contents generated with DocToc 第一讲 工作区和GOPATH 1. 环境变量配置2. 配置GOPATH的意义 2.1 Go语言源码的组织方式2.2 源码安装后的结果&#xff08;归档文件、可执行文…

开发大会上,前微软CEO放出的狠话!.NET开发随时起飞,你准备好了吗?

“开发者&#xff0c;开发者&#xff0c;开发者&#xff0c;开发者”&#xff0c;微软前任CEO史蒂夫鲍尔默(Steve Ballmer)用这种略带疯狂、又唱又跳的方式表达他对开发者的热爱。不夸张的说&#xff0c;相比二十年前那个如日中天的巨无霸微软&#xff0c;现在的微软比以往任何…

程序员过关斩将--为微服务撸一个简约而不简单的配置中心

点击上方蓝字 关注我们毫不犹豫的说&#xff0c;现代高速发展的互联网造就了一批又一批的网络红人&#xff0c;这一批批网红又极大的催生了特定平台的一大波流量&#xff0c;但是留给了程序员却是一地鸡毛&#xff0c;无论是运维还是开发&#xff0c;每天都会担心服务器崩溃&a…

Just a Hook HDU - 1698(查询区间求和+最基础模板)

题意&#xff1a; 给你一个1~n的区间&#xff0c;起始区间内均为1&#xff0c;然后对子区间进行值更新&#xff0c;最后求区间和。 题目&#xff1a; In the game of DotA, Pudge’s meat hook is actually the most horrible thing for most of the heroes. The hook is ma…

DDIA笔记——数据复制

Table of Contents generated with DocToc 此篇为《数据密集型应用系统设计》&#xff08;DDIA&#xff09;读书笔记&#xff0c;笔记可能存在遗漏&#xff0c;建议直接阅读原书。 第五章 数据复制 主从复制 复制滞后复制滞后带来的问题 多主节点复制 适用场景处理写冲突拓扑结…

基于 abp vNext 和 .NET Core 开发博客项目 - 集成Hangfire实现定时任务处理

上一篇文章成功使用了Redis缓存数据&#xff0c;大大提高博客的响应性能。接下来&#xff0c;将完成一个任务调度中心&#xff0c;关于定时任务有多种处理方式&#xff0c;如果你的需求比较简单&#xff0c;比如就是单纯的过多少时间循环执行某个操作&#xff0c;可以直接使用.…

Docker基本组成 和 基本命令

此篇为Docker笔记&#xff0c;文章可能存在疏忽&#xff0c;建议直接观看原视频。 视频地址&#xff1a;https://www.bilibili.com/video/BV1og4y1q7M4?spm_id_from333.999.0.0 Docker基本组成 和 基本命令 镜像 image&#xff1a;就好比一个模板&#xff0c;可以通过这个模板…

Docker镜像讲解

此篇为Docker笔记&#xff0c;文章可能存在疏忽&#xff0c;建议直接观看原视频。 视频地址&#xff1a;https://www.bilibili.com/video/BV1og4y1q7M4?spm_id_from333.999.0.0 参考&#xff1a;https://blog.csdn.net/11b202/article/details/21389067 Docker镜像讲解 镜像是…

Making the Grade POJ - 3666(离散化+dp)

题意&#xff1a; 给你n个山的高度&#xff0c;单独的一个数可以任意加减&#xff0c;让经过对每座山峰任意加减高度后变成递增或递减的序列时&#xff0c;求对每个数的相加或相减的数目的最小和。 题目&#xff1a; A straight dirt road connects two fields on FJ’s far…

Kubernetes的安全性怎么解?从4个方面为你列出方案清单

导语Kubernetes中的安全性是一个多维问题&#xff0c;必须从各个不同的角度来解决才算完善&#xff0c;这篇文章将从4个方面为读者列出安全清单。正文Kubernetes&#xff0c;经过更快的采用和社区的更多贡献&#xff0c;正日益攀登到新的高度。不过&#xff0c;安全性仍然是Kub…

DDIA笔记—第六章 数据分区

第六章 数据分区 数据分区与数据复制 分区通常与复制结合使用&#xff0c;即每个分区在多个节点都存在副本&#xff0c;这就意味着某条记录属于特定的分区&#xff0c;而同样的内容会保存在不同的节点上以提高系统的容错性。 每个节点同时充当某些分区的主副本和其他分区的从…

Magicodes.IE 2.2发布

Magicodes.IE 2.2发布导入导出通用库&#xff0c;支持DTO导入导出以及动态导出&#xff0c;支持Excel、Word、PDF、CSV和HTML。已加入ncc开源组织.Magicodes.IE2.0发布Magicodes.IE2.1发布如何做好一个开源项目(一)GitHub&#xff1a;https://github.com/dotnetcore/Magicodes.…

C++ 基类,子对象,派生类构造函数调用顺序

#include <iostream> using namespace std;class A {public:A( ) {cout << "A Constructor………" << endl;}~A( ) {cout << "A Destructor………" << endl;} };class B: public A {public:B( ) {cout << "B …

C++ 虚析构函数

代码如下: #include <iostream> using namespace std;class Base {public:Base() {cout << "Base" << endl;}~Base() {cout << "Base destructor" << endl;} };class Derived : public Base {public:Derived() {cout <&…

I - Interesting Permutation Gym - 102394I(排列组合)

题意&#xff1a; 纯数题 1≤i≤n, fimax{a1,a2,…,ai}; 1≤i≤n, gimin{a1,a2,…,ai}; 1≤i≤n, hifi−gi. 数列a是一个排列&#xff0c;问多少种排列方式满足h数列。 题目&#xff1a; DreamGrid has an interesting permutation of 1,2,…,n denoted by a1,a2,…,an. He …

Magicodes.SwaggerUI 已支持.NET Core 3.1

Magicodes.SwaggerUI 通过配置文件简单配置即可快速完成SwaggerUI的配置&#xff0c;包括&#xff1a;SwaggerUI的文档信息API分组API隐藏API JSON生成&#xff08;枚举、API架构Id&#xff09;验证自定义页面支持.NET Core 2.2和3.1。版本日志和使用教程见下文。注意&#xff…