系列文章
- 基于 abp vNext 和 .NET Core 開發博客專案 - 使用 abp cli 搭建專案
- 基于 abp vNext 和 .NET Core 開發博客專案 - 給專案瘦身,讓它跑起來
- 基于 abp vNext 和 .NET Core 開發博客專案 - 完善與美化,Swagger登場
- 基于 abp vNext 和 .NET Core 開發博客專案 - 資料訪問和代碼優先
- 基于 abp vNext 和 .NET Core 開發博客專案 - 自定義倉儲之增刪改查
- 基于 abp vNext 和 .NET Core 開發博客專案 - 統一規范API,包裝回傳模型
- 基于 abp vNext 和 .NET Core 開發博客專案 - 再說Swagger,分組、描述、小綠鎖
- 基于 abp vNext 和 .NET Core 開發博客專案 - 接入GitHub,用JWT保護你的API
- 基于 abp vNext 和 .NET Core 開發博客專案 - 例外處理和日志記錄
- 基于 abp vNext 和 .NET Core 開發博客專案 - 使用Redis快取資料
- 基于 abp vNext 和 .NET Core 開發博客專案 - 集成Hangfire實作定時任務處理
- 基于 abp vNext 和 .NET Core 開發博客專案 - 用AutoMapper搞定物件映射
- 基于 abp vNext 和 .NET Core 開發博客專案 - 定時任務最佳實戰(一)
- 基于 abp vNext 和 .NET Core 開發博客專案 - 定時任務最佳實戰(二)
- 基于 abp vNext 和 .NET Core 開發博客專案 - 定時任務最佳實戰(三)
- 基于 abp vNext 和 .NET Core 開發博客專案 - 博客介面實戰篇(一)
- 基于 abp vNext 和 .NET Core 開發博客專案 - 博客介面實戰篇(二)
- 基于 abp vNext 和 .NET Core 開發博客專案 - 博客介面實戰篇(三)
- 基于 abp vNext 和 .NET Core 開發博客專案 - 博客介面實戰篇(四)
- 基于 abp vNext 和 .NET Core 開發博客專案 - 博客介面實戰篇(五)
- 基于 abp vNext 和 .NET Core 開發博客專案 - Blazor 實戰系列(一)
- 基于 abp vNext 和 .NET Core 開發博客專案 - Blazor 實戰系列(二)
- 基于 abp vNext 和 .NET Core 開發博客專案 - Blazor 實戰系列(三)
- 基于 abp vNext 和 .NET Core 開發博客專案 - Blazor 實戰系列(四)
- 基于 abp vNext 和 .NET Core 開發博客專案 - Blazor 實戰系列(五)
- 基于 abp vNext 和 .NET Core 開發博客專案 - Blazor 實戰系列(六)
- 基于 abp vNext 和 .NET Core 開發博客專案 - Blazor 實戰系列(七)
- 基于 abp vNext 和 .NET Core 開發博客專案 - Blazor 實戰系列(八)
- 基于 abp vNext 和 .NET Core 開發博客專案 - Blazor 實戰系列(九)
- 基于 abp vNext 和 .NET Core 開發博客專案 - 終結篇之發布專案
上一篇文章(https://www.cnblogs.com/meowv/p/12943699.html)完成了專案的全域例外處理和日志記錄,
在日志記錄中使用的靜態方法有人指出寫法不是很優雅,遂優化一下上一篇中日志記錄的方法,具體操作如下:
在.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());
else
result.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
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/24087.html
標籤:.NET Core
