随着项目关注度渐渐升高,目前已经1.2k个star,我的内心反而更加的惶恐了起来,最近也是很有强迫症,只要有小伙伴反馈项目的问题,就很着急,哪怕一丁点的问题,就需要思考很久,生怕是底层设计的问题,导致项目有不可挽回的后果,那就断送了整个项目的发展了。
不过目前还好吧,除了数据库连接偶尔有人反馈说,在异步并发中,会间歇性的出现连接关闭的问题,其他没有发现什么。当然如果感觉这个ORM不好用,换成EFCore也是一样的,其他的功能还是很抗的住,无论是DI、Filter、AOP还是授权。
但是就在前两天,我在优化代码的时候,为了做压测,把所有的附加功能都关了,当然缓存AOP也关闭了:
当时是没有考虑很多,就把代码提交到了远程Github,没想到引发了一次疑案,很凑巧,刚刚提交上去,立刻就有一个小伙伴反应了问题,说报错了,然后开了一个bug:
具体的错误场景是这样的,其他页面很正常,怎么刷新都没事儿,唯独【权限分配】页面报错了:
其实说实话,很久之前有人断断续续的问过这个小问题,但是我一直没有复现出来,所有就没办法去修改,这次正好有一个小伙伴遇到了,我当时一想,肯定是他自己修改了什么,导致出错了,我下载下来测试一下,就知道了。 没想到真的报错了,当时瞬间就感觉慌了,代码逻辑肯定是没有问题的,毕竟是写了一年了,也有很多人在使用,那这种幽灵问题是为何,如果一个项目出现幽灵bug,那是很纠心又难受的,所以,我决定让自己冷静冷静,好好的检测检测,故事就开始了。 今天不会去讲解什么是JWT,什么是授权,什么是自定义复杂策略授权,这些基本概念,可以看我的视频或者文章,今天主要说说,在复杂策略授权中,遇到的小问题。
01
到底是哪里的问题?
我看了一下错误报告,是这样的:
大概意思就是,通过sqlsugar请求的时候,因为我是策略授权,所以在PermissionHandler中,增加动态从数据库获取角色和接口的映射关系,所以现在在请求的时候,报错reader已经关闭了,不能再重复访问了,我当时一脸蒙圈。不会是Sqlsugar有问题吧,这要是底层的问题,可就难受了。看着情况应该是重复使用实例对象了,但是我service层用的是autofac注入没有问题呀,而且也是scoped的。
现在是找到了问题所在,就是我们的策略授权中,使用了
代码语言:javascript复制await _roleModulePermissionServices.RoleModuleMaps();
来获取角色菜单关系的缘故,下边我们就是根据问题来找方案了。
02
如何解决这个问题?
当时我就思考着,为何之前没有遇到过,是因为之前我用的是AOP缓存,这样每次请求,其实请求的是缓存的数据,所以不会出现重复使用数据库的DataReader,那方案以就出现了,我也是建议小伙伴这么弄的:
这样确实没有问题了,毕竟第一次是请求,后边十分钟都是在调用的缓存数据,肯定不会出现重复使用,但是这么说,心里没有底气呀,感觉自己都不好意思看到这个答案,夜里久久不能寐,继续研究。
夜里突然想起来一个问题,既然出现了问题,就要从问题根本着手处理,到底为什么会重复调用同一个DataReader?那肯定有一个地方用单例了,但是我的Service层就是Scope呀,继续研究,发现了问题所在。
我虽然Service是Scope的,但是授权处理器PermissionHandler当时图省事儿,设置了单例!
所以,这也就是导致了我们的Service是同一个实例了,而且很凑巧的是,Admin项目中的【权限分配】页里,正好同时发起了两次请求,就报错了。
解决方案就是把PermissionHandler的单例注入,改成Scoped注入即可:
03
进一步深入优化。
上边的改好了以后,我就深入的想了想,在PermissionHandler处理程序中,既然要获取全部的用户菜单关系,而且还是单例的,那为啥每次都要请求一次呢?登录的时候,获取一次不就行了?
代码语言:javascript复制var data = await _roleModulePermissionServices.RoleModuleMaps();
我们删除PermissionHandler中的相关代码,把他们移动到了登录页,获取token的时候:
代码语言:javascript复制 var user = await _sysUserInfoServices.Query(d => d.uLoginName == name && d.uLoginPWD == pass);
if (user.Count > 0)
{
var userRoles = await _sysUserInfoServices.GetUserRoleNameStr(name, pass);
//如果是基于用户的授权策略,这里要添加用户;如果是基于角色的授权策略,这里要添加角色
var claims = new List<Claim> {
new Claim(ClaimTypes.Name, name),
new Claim(JwtRegisteredClaimNames.Jti, user.FirstOrDefault().uID.ToString()),
new Claim(ClaimTypes.Expiration, DateTime.Now.AddSeconds(_requirement.Expiration.TotalSeconds).ToString()) };
claims.AddRange(userRoles.Split(',').Select(s => new Claim(ClaimTypes.Role, s)));
// 就是这里!!!
var data = await _roleModulePermissionServices.RoleModuleMaps();
var list = (from item in data
where item.IsDeleted == false
orderby item.Id
select new PermissionItem
{
Url = item.Module?.LinkUrl,
Role = item.Role?.Name,
}).ToList();
_requirement.Permissions = list;
var token = JwtToken.BuildJwtToken(claims.ToArray(), _requirement);
return new JsonResult(token);
}
这样的话,我们登录的时候,更新一次,然后其他的时候,就不重复获取了,但是这样有个小小的问题,就是如果token有效,管理员在后端修改相应的菜单权限的话,就必须重新登录了,但是也无伤大雅,我已经在代码中注释。
那这样的话,我们就不用把PermissionHandler的依赖注入方式改成Scope了,这样也会每次都实例化,干脆还是改成单例,毕竟我们不用在授权处理程序中获取角色菜单关系了。
所以最后我的两个方案是:
1、在LoginController的登录api中,获取 _roleModulePermissionServices.RoleModuleMaps(); 同时,注入的方式,改成Singleton; 2、还是在PermissionHandler中获取角色菜单Map,但是注入的方式一定要是Scope的。
04
打完收工
目前更新已经一天了,没有发现问题,有一个bug解决了。
Blog.Core开源项目地址https://github.com/anjoy8/Blog.Core