目录
目录
1.JSON序列化之long 长整型数据精度丢失设置
2.API接口请求速率限制配置
3.JWT Token 认证
3.1登录验证码
3.2用户登录实现
3.3JWT身份认证实现
4.日志
4.1错误日志
4.2请求审计日志
4.3日志订阅
5.用户权限配置
6.附录
关于项目框架的基础搭建参见开发框架Furion之WebApi+SqlSugar (一)
有时候我们需要将 long
类型序列化时转为 string
类型,防止 JavaScript
出现精度溢出问题
MyFurion.Start项目中的Startup.cs ConfigureServices中配置如下代码
System.Text.Json
方式
services.AddControllersWithViews().AddJsonOptions(options =>
{
//long类型数据防止精度丢失设置
options.JsonSerializerOptions.Converters.AddLongTypeConverters();
});
Newtonsoft.Json
方式
services.AddControllersWithViews().AddNewtonsoftJson(options =>
{
//统一日期类型返回
options.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:mm:ss";
//long数据类型精度丢失问题解决
options.SerializerSettings.Converters.AddLongTypeConverters();
});
关于更多的JSON序列化配置参见 23. JSON 序列化 | Furion
什么是限速
限速(Rate-Limiting)系统可以控制网络接口发送和接受流量的速率。对于Web API来说,限速系统被用来控制一段时间内某个程序或客户端允许调用某个API的次数,超过该次数的流量会被拒绝。例如Github的API只允许开发者每小时发送5000次请求。
限速策略
在对API限速之前,您首先要考虑好限速的策略,通常一个好的限速策略有以下这两个特性:
限速策略考虑还需考虑的问题:
针对ASP.NET Core Web API项目,如果不采用网关的话考虑使用AspNetCoreRateLimit
限速实现
在MyFurion.Start项目中,通过Nuget添加 AspNetCoreRateLimit,然后在Handlers文件夹中创建IPRateExtension
using AspNetCoreRateLimit;
namespace MyFurion.Start
{
public static class IPRateExtension
{
public static void AddIPRate(this IServiceCollection services)
{
if (services == null) throw new ArgumentNullException(nameof(services));
//从appsettings.json中加载常规配置,IpRateLimiting与配置文件中节点对应
services.Configure(App.Configuration.GetSection("IpRateLimiting"));
//从appsettings.json中加载Ip规则
services.Configure(App.Configuration.GetSection("IpRateLimitPolicies"));
//注入计数器和规则存储
services.AddSingleton();
services.AddSingleton();
//配置(解析器、计数器密钥生成器)
services.AddSingleton();
services.AddSingleton();
}
}
}
Startup中的ConfigureServices中添加代码
services.AddIPRate();//ip请求频率限制
Startup中的Configure中添加代码
app.UseIpRateLimiting();//启用客户端IP限制速率
appsettings.json配置文件中配置限制参数
//接口请求IP速率限制
"IpRateLimiting": {
//例如设置了5次每分钟访问限流。当False时:项目中每个接口都加入计数,不管你访问哪个接口,只要在一分钟内累计够5次,将禁止访问。
//True:当一分钟请求了5次GetData接口,则该接口将在时间段内禁止访问,但是还可以访问PostData()5次,总得来说是每个接口都有5次在这一分钟,互不干扰。
"EnableEndpointRateLimiting": true,
//false,拒绝的API调用不会添加到调用次数计数器上;如 客户端每秒发出3个请求并且您设置了每秒一个调用的限制,则每分钟或每天计数器等其他限制将仅记录第一个调用,即成功的API调用。如果您希望被拒绝的API调用计入其他时间的显示(分钟,小时等)
//则必须设置StackBlockedRequests为true。
"StackBlockedRequests": false,
"RealIpHeader": "X-Real-IP",
//取白名单的客户端ID。如果此标头中存在客户端ID并且与ClientWhitelist中指定的值匹配,则不应用速率限制。
"ClientIdHeader": "X-ClientId",
"HttpStatusCode": 429, //返回状态码
//端点白名单
"EndpointWhitelist": [], //"post:/api/sysAuth/login","*:/logout"
//返回消息内容
"QuotaExceededResponse": {
"Content": "{{\"success\":false,\"code\":429,\"message\":\"访问过于频繁,请稍后重试\",\"data\":null}}",
"ContentType": "application/json",
"StatusCode": 429
},
//通用规则,api规则,结尾一定要带*
"GeneralRules": [
{
"Endpoint": "*",
"Period": "3s", //时间段,格式:{数字}{单位};可使用单位:s, m, h, d
"Limit": 50//限制访问的次数
}
],
"IpRateLimitPolicies": {
//ip规则
"IpRules": [
]
}
}
MyFurion.Unility项目,通过Nuget添加ZKWeb.System.Drawing,创建Generic文件夹
然后在Generic文件夹下创建CaptchaHelper.cs类文件,实现登录验证码的创建
using System.ComponentModel;
using System.DrawingCore;
using System.DrawingCore.Imaging;
namespace MyFurion.Unility.Generic
{
///
/// 验证码
///
public class CaptchaHelper
{
///
/// 获取验证码
///
/// 验证码数
/// 类型 0:数字 1:字符
///
public static VerifyCode CreateVerifyCode(int n, VerifyCodeType type)
{
int codeW = 170;//宽度
int codeH = 50;//高度
int fontSize = 32;//字体大小
//初始化验证码
string charCode = string.Empty;
string resultCode = "";
switch (type.ToString())
{
case "NUM":
charCode = CreateNumCode(n);
break;
case "ARITH":
charCode = CreateArithCode(out resultCode);
n = charCode.Length;
break;
default:
charCode = CreateCharCode(n);
break;
}
//颜色列表
Color[] colors = { Color.Black, Color.Red, Color.Blue, Color.Green, Color.Orange, Color.Brown, Color.DarkBlue };
//字体列表
string[] fonts = { "Times New Roman", "Verdana", "Arial", "Gungsuh" };
//创建画布
Bitmap bitmap = new Bitmap(codeW, codeH);
Graphics graphics = Graphics.FromImage(bitmap);
graphics.Clear(Color.White);
Random random = new Random();
//画躁线
for (int i = 0; i < n; i++)
{
int x1 = random.Next(codeW);
int y1 = random.Next(codeH);
int x2 = random.Next(codeW);
int y2 = random.Next(codeH);
Color color = colors[random.Next(colors.Length)];
Pen pen = new Pen(color);
graphics.DrawLine(pen, x1, y1, x2, y2);
}
//画噪点
for (int i = 0; i < 100; i++)
{
int x = random.Next(codeW);
int y = random.Next(codeH);
Color color = colors[random.Next(colors.Length)];
bitmap.SetPixel(x, y, color);
}
//画验证码
for (int i = 0; i < n; i++)
{
string fontStr = fonts[random.Next(fonts.Length)];
Font font = new Font(fontStr, fontSize);
Color color = colors[random.Next(colors.Length)];
graphics.DrawString(charCode[i].ToString(), font, new SolidBrush(color), (float)i * 30 + 5, (float)0);
}
//写入内存流
try
{
MemoryStream stream = new MemoryStream();
bitmap.Save(stream, ImageFormat.Jpeg);
VerifyCode verifyCode = new VerifyCode()
{
Code = type.ToString() == "ARITH" ? resultCode : charCode,
Image = stream.ToArray()
};
return verifyCode;
}
//释放资源
finally
{
graphics.Dispose();
bitmap.Dispose();
}
}
///
/// 获取数字验证码
///
/// 验证码数
///
public static string CreateNumCode(int n)
{
char[] numChar = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };
string charCode = string.Empty;
Random random = new Random();
for (int i = 0; i < n; i++)
{
charCode += numChar[random.Next(numChar.Length)];
}
return charCode;
}
///
/// 获取字符验证码
///
/// 验证码数
///
public static string CreateCharCode(int n)
{
char[] strChar = { 'a', 'b','c','d','e','f','g','h','i','j','k','l','m',
'n','o','p','q','r','s','t','u','v','w','x','y','z','0','1','2','3',
'4','5','6','7','8','9','A','B','C','D','E','F','G','H','I','J','K',
'L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'};
string charCode = string.Empty;
Random random = new Random();
for (int i = 0; i < n; i++)
{
charCode += strChar[random.Next(strChar.Length)];
}
return charCode;
}
///
/// 获取运算符验证码
///
///
public static string CreateArithCode(out string resultCode)
{
string checkCode = "";
Random random = new Random();
int intFirst = random.Next(1, 20);//生成第一个数字
int intSec = random.Next(1, 20);//生成第二个数字
int intTemp = 0;
switch (random.Next(1, 3).ToString())
{
case "2":
if (intFirst < intSec)
{
intTemp = intFirst;
intFirst = intSec;
intSec = intTemp;
}
checkCode = intFirst + "-" + intSec + "=";
resultCode = (intFirst - intSec).ToString();
break;
default:
checkCode = intFirst + "+" + intSec + "=";
resultCode = (intFirst + intSec).ToString();
break;
}
return checkCode;
}
}
///
/// 验证码信息
///
public class VerifyCode
{
///
/// 验证码
///
public string Code { get; set; }
///
/// 验证码数据流
///
public byte[] Image { get; set; }
///
/// base64
///
public string Base64Str { get { return Convert.ToBase64String(Image); } }
}
///
/// 验证码类型
///
public enum VerifyCodeType
{
[Description("纯数字验证码")]
NUM = 0,
[Description("数字加字母验证码")]
CHAR = 1,
[Description("数字运算验证码")]
ARITH = 2,
}
}
redis缓存配置
MyFurion.Unility项目,通过Nuget 添加Microsoft.Extensions.Caching.StackExchangeRedis
Generic文件加下创建CacheHelper类文件
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Distributed;
namespace MyFurion.Unility.Generic
{
///
/// Microsoft.Extensions.Caching.redis 缓存
///
public class CacheHelper
{
private readonly IDistributedCache _cache;
public CacheHelper(IDistributedCache cache)
{
_cache = cache;
}
///
/// 设置缓存
///
///
///
/// 过期时间 单位秒
public void SetRedisCache(string key,string value,int time)
{
_cache.Set(key, Encoding.UTF8.GetBytes(value), new DistributedCacheEntryOptions().SetSlidingExpiration(TimeSpan.FromSeconds(time)));
}
///
/// 获取缓存
///
///
///
public string GetReidsCache(string key)
{
byte[]?valueByte=_cache.Get(key);
return valueByte != null ? Encoding.Default.GetString(valueByte) : null;
}
///
/// 删除缓存
///
///
public void DelRedisCache(string key)
{
_cache.Remove(key);
}
}
}
appsettings.json配置文件中配置Redis数据库连接
"RedisConnection": "127.0.0.1:6379,defaultDatabase=0,ssl=false,writeBuffer=10240"
MyFurion.Start项目,ConfigureServices中,配置redis连接
//redis 缓存配置
services.AddStackExchangeRedisCache(options =>
{
options.Configuration = App.GetConfig("RedisConnection");// 连接字符串
options.InstanceName = "furion_"; // 键名前缀
});
登录接口实现
MyFurion.Model中创建UserInfo实体对象,在MyFurion.Application项目创建用户仓储及用户登录接口
MyFurion.Application项目, 通过Nuget 添加Furion.Extras.Authentication.JwtBearer
用户实体对象
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MyFurion.Model
{
///
/// 用户信息
///
[SugarTable("Sys_User")]
[Tenant(0)]
public class UserInfo:BaseEntity
{
///
/// 用户姓名
///
[SugarColumn(IsNullable =true,ColumnDescription = "用户姓名")]
public string? UserName { get; set; }
///
/// 用户昵称
///
[SugarColumn(IsNullable = true, ColumnDescription = "用户昵称")]
public string? NickName { get; set; }
///
/// 登录账户
///
[SugarColumn(IsNullable = false, ColumnDescription = "登录账户")]
public string? Account { get; set; }
///
/// 登录密码
///
[SugarColumn(IsNullable = false, ColumnDescription = "登录密码")]
public string? LoginPwd { get; set; }
///
/// 联系电话
///
[SugarColumn(IsNullable = true, ColumnDescription = "联系电话")]
public string? Tel { get; set; }
///
/// 电子邮箱
///
[SugarColumn(IsNullable = true, ColumnDescription = "电子邮箱")]
public string? Email { get; set; }
}
}
用户仓储
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MyFurion.Application
{
///
/// 用户信息服务仓储
///
public class UserRepository:BaseRepository,ITransient
{
//TODO
}
}
appsettings.json配置JWT秘钥、加密方法等
"JWTSettings": {
"IssuerSigningKey": "Furion@#PassWord!@2022", // 密钥,string 类型,必须是复杂密钥,长度大于16
"ValidIssuer": "Furion@2022", // 签发方,string 类型
"ValidAudience": "Furion@Client", // 签收方,string 类型
"ExpiredTime": 30 // 过期时间,long 类型,单位分钟,默认20分钟5秒
//"Algorithm": "HS256" // 加密算法,string 类型,默认 HS256
}
登录接口
using Microsoft.Extensions.Caching.Distributed;
namespace MyFurion.Application.Controller
{
///
/// Login
///
[ApiDescriptionSettings(Name = "Login", Order = 2)]
[Route("api/sysLogin")]
public class LoginController:IDynamicApiController
{
private readonly UserRepository _userRepository;
private readonly IDistributedCache _cache;
private readonly IHttpContextAccessor _httpContextAccessor;
public LoginController(UserRepository userRepository, IDistributedCache cache, IHttpContextAccessor httpContextAccessor)
{
_userRepository = userRepository;
_cache = cache;
_httpContextAccessor = httpContextAccessor;
}
///
/// 获取验证码
///
///
[AllowAnonymous]
[HttpGet("getCaptcha")]
public object GetCaptcha()
{
string uuid = Guid.NewGuid().ToString().Replace("-", "");
CacheHelper cacheHelper = new CacheHelper(_cache);
var verifyCode = CaptchaHelper.CreateVerifyCode(4, VerifyCodeType.CHAR);
cacheHelper.SetRedisCache(uuid, verifyCode.Code, 300);
return new { uuid, img = verifyCode.Base64Str };
}
///
/// 登录
///
///
///
[AllowAnonymous]
[HttpPost("login")]
public async Task Login(LoginInput input)
{
//验证码校验
CacheHelper cacheHelper = new CacheHelper(_cache);
string verifyCode = cacheHelper.GetReidsCache(input.CaptchaId);
if (!string.IsNullOrWhiteSpace(verifyCode))
{
if (!verifyCode.ToLower().Equals(input.Captcha.ToLower()))
{
throw Oops.Oh("验证码错误");
}
else
{
cacheHelper.DelRedisCache(input.CaptchaId);
}
}
else
{
throw Oops.Oh("验证码已失效");
}
//登录账号校验
string md5Pwd= MD5Encryption.Encrypt(input.Password);
var userInfo = await _userRepository.GetFirstAsync(it=>it.Account==input.LoginName&&it.LoginPwd==md5Pwd);
if (userInfo == null)
{
throw Oops.Oh("用户名或密码错误");
}
var accessToken = JWTEncryption.Encrypt(new Dictionary
{
{"UserId", userInfo.Id},
{"UserName", userInfo.UserName},
{"NickName", userInfo.NickName}
});
// 设置Swagger自动登录
_httpContextAccessor.HttpContext.SigninToSwagger(accessToken);
// 生成刷新Token令牌
var refreshToken = JWTEncryption.GenerateRefreshToken(accessToken);
// 设置刷新Token令牌
_httpContextAccessor.HttpContext.Response.Headers["x-access-token"] = refreshToken;
return accessToken;
}
}
}
MyFurion.Start项目中,Handlers文件夹创建JwtHandler类文件
using Furion.Authorization;
using Furion.DataEncryption;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
namespace MyFurion.Start
{
///
/// JWT身份认证
///
public class JwtHandler : AppAuthorizeHandler
{
///
/// 管道请求
///
///
///
///
public override Task PipelineAsync(AuthorizationHandlerContext context, DefaultHttpContext httpContext)
{
// 这里写您的授权判断逻辑,授权通过返回 true,否则返回 false
return Task.FromResult(true);
}
///
/// 重写 Handler 添加自动刷新
///
///
///
public override async Task HandleAsync(AuthorizationHandlerContext context)
{
// 自动刷新Token
if (JWTEncryption.AutoRefreshToken(context, context.GetCurrentHttpContext(),App.GetOptions().ExpiredTime))
{
await AuthorizeHandleAsync(context);
}
else
{
context.Fail(); // 授权失败
DefaultHttpContext currentHttpContext = context.GetCurrentHttpContext();
if (currentHttpContext == null)
{
return;
}
currentHttpContext.SignoutToSwagger();
}
}
}
}
Startup中的ConfigureServices启用JWT身份认证
services.AddJwt(enableGlobalAuthorize: true);//启用Jwt 身份验证 全局权限
Configure中启用身份认证
app.UseAuthentication();
app.UseAuthorization();
关于日志,使用IEventSubscriber时间订阅依赖接口实现,将日志存储到数据库中
MyFurion.Model项目,创建ErrorLog类文件,MyFurion.Application项目创建ErrorLog仓储
namespace MyFurion.Model
{
///
/// 错误日志
///
[SugarTable("Sys_ErrorLog")]
[Tenant(0)]
public class ErrorLog:BaseEntity
{
///
/// 类名
///
[SugarColumn(Length = 100, IsNullable = true, ColumnDescription = "类名")]
public string ClassName { get; set; }
///
/// 方法名
///
[SugarColumn(Length = 100, IsNullable = true, ColumnDescription = "方法名")]
public string MethodName { get; set; }
///
/// 异常名称
///
[SugarColumn(IsNullable = true, ColumnDataType = CommonConst.DB_STRING_MAX, ColumnDescription = "异常名称")]
public string ExceptionName { get; set; }
///
/// 异常信息
///
[SugarColumn(IsNullable = true, ColumnDataType = CommonConst.DB_STRING_MAX, ColumnDescription = "异常信息")]
public string ExceptionMsg { get; set; }
///
/// 异常源
///
[SugarColumn(IsNullable = true, ColumnDataType = CommonConst.DB_STRING_MAX, ColumnDescription = "异常源")]
public string ExceptionSource { get; set; }
///
/// 堆栈信息
///
[SugarColumn(IsNullable = true, ColumnDataType = CommonConst.DB_STRING_MAX, ColumnDescription = "堆栈信息")]
public string StackTrace { get; set; }
///
/// 参数对象
///
[SugarColumn(IsNullable = true, ColumnDataType = CommonConst.DB_STRING_MAX, ColumnDescription = "参数对象")]
public string ParamsObj { get; set; }
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MyFurion.Application
{
///
/// 错误日志服务仓储
///
public class ErrorLogRepository : BaseRepository, ITransient
{
//TODO
}
}
MyFurion.Model项目,创建RequestLog类文件,MyFurion.Application项目创建RequestLog仓储
namespace MyFurion.Model
{
///
/// 请求审计日志
///
[SugarTable("Sys_RequestLog")]
[Tenant(0)]
public class RequestLog:BaseEntity
{
///
/// 是否执行成功
///
[SugarColumn(ColumnDescription = "是否执行成功")]
public bool Success { get; set; }
///
/// 具体消息
///
[SugarColumn(IsNullable = true, ColumnDataType = CommonConst.DB_STRING_MAX, ColumnDescription = "具体消息")]
public string Message { get; set; }
///
/// IP
///
[SugarColumn(Length = 100, IsNullable = true, ColumnDescription = "ip地址")]
public string Ip { get; set; }
///
/// 地址
///
[SugarColumn(Length = 1024, IsNullable = true, ColumnDescription = "地址")]
public string Location { get; set; }
///
/// 浏览器
///
[SugarColumn(Length = 100, IsNullable = true, ColumnDescription = "浏览器")]
public string Browser { get; set; }
///
/// 操作系统
///
[SugarColumn(Length = 100, IsNullable = true, ColumnDescription = "操作系统")]
public string OsSystem { get; set; }
///
/// 请求地址
///
[SugarColumn(Length = 100, IsNullable = true, ColumnDescription = "请求地址")]
public string Url { get; set; }
///
/// 类名称
///
[SugarColumn(Length = 100, IsNullable = true, ColumnDescription = "类名称")]
public string ClassName { get; set; }
///
/// 方法名称
///
[SugarColumn(Length = 100, IsNullable = true, ColumnDescription = "方法名称")]
public string MethodName { get; set; }
///
/// 请求方式(GET POST PUT DELETE)
///
[SugarColumn(Length = 100, IsNullable = true, ColumnDescription = "请求方式")]
public string ReqMethod { get; set; }
///
/// 请求参数
///
[SugarColumn(IsNullable = true, ColumnDataType = CommonConst.DB_STRING_MAX, ColumnDescription = "请求参数")]
public string Param { get; set; }
///
/// 返回结果
///
[SugarColumn(IsNullable = true, ColumnDataType = CommonConst.DB_STRING_MAX, ColumnDescription = "返回结果")]
public string Result { get; set; }
///
/// 耗时(毫秒)
///
[SugarColumn(ColumnDescription = " 耗时(毫秒)")]
public long ElapsedTime { get; set; }
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MyFurion.Application
{
///
/// 请求审计日志服务仓储
///
public class RequestLogRepository : BaseRepository, ITransient
{
//TODO
}
}
MyFurion.Unility项目,Nuget添加Microsoft.AspNetCore.Http.Abstractions
MyFurion.Unility项目,创建获取IP地址类IPHelper
using Microsoft.AspNetCore.Http;
namespace MyFurion.Unility.Generic
{
public static class IPHelper
{
///
/// 获取请求的ip4
///
///
///
public static string GetRequestIPv4(this HttpContext context)
{
string ip = string.Empty;
if (context.Connection.RemoteIpAddress != null)
{
if (context.Request.Headers.ContainsKey("X-Real-IP"))
{
ip = context.Request.Headers["X-Real-IP"].FirstOrDefault();
}
if (context.Request.Headers.ContainsKey("X-Forwarded-For"))
{
ip = context.Request.Headers["X-Forwarded-For"].FirstOrDefault();
}
if (string.IsNullOrEmpty(ip))
{
ip = context.Connection.RemoteIpAddress?.MapToIPv4()?.ToString();
}
}
return ip;
}
}
}
MyFurion.Start项目,Nuget添加UAParser
MyFurion.Start项目,创建LogEventSubscriber日志事件订阅类文件
using MyFurion.Application;
using Furion.EventBus;
namespace MyFurion.Start
{
///
/// 日志事件订阅依赖接口
///
public class LogEventSubscriber : IEventSubscriber
{
///
/// 请求日志
///
///
///
[EventSubscribe("Add:RequestLog")]
public async Task AddWorkLog(EventHandlerExecutingContext context)
{
var log = (RequestLog)context.Source.Payload;
await App.GetService().Add(log);
}
///
/// 错误日志
///
///
///
[EventSubscribe("Add:ErrorLog")]
public async Task AddErrorLog(EventHandlerExecutingContext context)
{
var log = (ErrorLog)context.Source.Payload;
await App.GetService().Add(log);
}
}
}
MyFurion.Start项目,创建全局异常处理类ErrorLogFilter
using Furion.DependencyInjection;
using Furion.EventBus;
using Furion.FriendlyException;
using Microsoft.AspNetCore.Mvc.Filters;
namespace MyFurion.Start
{
///
/// 全局异常处理
///
public class ErrorLogFilter : IGlobalExceptionHandler, ISingleton
{
private readonly IEventPublisher _eventPublisher;
///
/// 全局异常处理
///
///
public ErrorLogFilter(IEventPublisher eventPublisher)
{
_eventPublisher = eventPublisher;
}
///
/// 异常处理
///
///
///
public async Task OnExceptionAsync(ExceptionContext context)
{
//var userContext = App.User;
await _eventPublisher.PublishAsync(new ChannelEventSource("Add:ErrorLog",
new ErrorLog
{
//Account = userContext?.FindFirstValue(ClaimConst.ClAIM_ACCOUNT) ?? String.Empty,
//ErrorName = userContext?.FindFirstValue(ClaimConst.ClAIM_NAME) ?? String.Empty,
ClassName = context.Exception.TargetSite?.DeclaringType?.FullName,
MethodName = context.Exception.TargetSite?.Name ?? String.Empty,
ExceptionName = context.Exception.Message,
ExceptionMsg = context.Exception.Message,
ExceptionSource = context.Exception.Source,
StackTrace = context.Exception.StackTrace,
ParamsObj = context.Exception.TargetSite?.GetParameters().ToString()
}));
// 写日志文件
Log.Error(context.Exception.ToString());
}
}
}
MyFurion.Start项目,创建请求日志处理类 RequestLogFilter
using Furion.EventBus;
using Furion.JsonSerialization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Filters;
using System.Diagnostics;
using UAParser;
using MyFurion.Unility.Generic;
namespace MyFurion.Start
{
///
/// 请求日志
///
public class RequestLogFilter : IAsyncActionFilter
{
private readonly IEventPublisher _eventPublisher;
///
/// 请求日志拦截
///
///
public RequestLogFilter(IEventPublisher eventPublisher)
{
_eventPublisher = eventPublisher;
}
///
/// 请求处理(执行操作前后)
///
///
///
///
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
var httpContext = context.HttpContext;
var httpRequest = httpContext.Request;
var sw = new Stopwatch();
sw.Start();
var actionContext = await next();
sw.Stop();
// 判断是否请求成功(没有异常就是请求成功)
var isRequestSucceed = actionContext.Exception == null;
var headers = httpRequest.Headers;
var clientInfo = headers.ContainsKey("User-Agent") ? Parser.GetDefault().Parse(headers["User-Agent"]) : null;
var actionDescriptor = context.ActionDescriptor as ControllerActionDescriptor;
var ip = httpContext.GetRequestIPv4();
await _eventPublisher.PublishAsync(new ChannelEventSource("Add:RequestLog",
new RequestLog
{
Success=isRequestSucceed,
Ip = ip,
Location = httpRequest.GetRequestUrlAddress(),
Browser = clientInfo?.UA.Family + clientInfo?.UA.Major,
OsSystem = clientInfo?.OS.Family + clientInfo?.OS.Major,
Url = httpRequest.Path,
ClassName = context.Controller.ToString() ?? String.Empty,
MethodName = actionDescriptor?.ActionName ?? String.Empty,
ReqMethod = httpRequest.Method,
Param = context.ActionArguments.Count < 1 ? string.Empty : JSON.Serialize(context.ActionArguments),
Result = actionContext.Result?.GetType() == typeof(JsonResult) ? JSON.Serialize(actionContext.Result) : string.Empty,
ElapsedTime = sw.ElapsedMilliseconds
}));
}
}
}
MyFurion.Start项目,Startup ConfigureServices注册日志订阅事件及请求日志
// 注册EventBus服务
services.AddEventBus(builder =>
{
// 注册 Log 日志订阅者
builder.AddSubscriber();
});
//全局注册请求日志
services.Configure(options =>
{
options.Filters.Add();
});
一般我们用到的权限配置是菜单权限、按钮操作权限、数据域权限,此处不做详细说明了,大家可根据实际情况自行设计开发
主要最终版源码的可以至MyFurion: 项目使用Furion开源框架、SqlSugar开源多库架构ORM框架、.Net6,用户登录、角色、权限、部门、菜单、数据字典、日志、多租户等基础功能的实现
下载