有小伙伴说PasteForm示例代码中的权限层级设计得很好,希望用于其他地方,问下有没有更简单得办法
每次都复制修改,有点麻烦,还容易出错!
小伙伴说得应该就是权限得多层级,一层套一层得那个功能,在多层级上,确实比递归好,而且在表格中做到了按序排序!!!
这个需求可不太好实现,如果用常规做法得话!
同一层得排序,子集排序。。。
之前我们不是写了一个归属查询么,就是用一个interface,所以我们这一次也这么干
提取要用得数据,作为多层级得Entity得接口
/// <summary>
/// 支持 非递归多级查询,支持多级排序
/// </summary>
public interface ILevelEntity
{
/// <summary>
/// 标识
/// </summary>
int Id { get; set; }
/// <summary>
/// 排序串
/// </summary>
string SortStr { get; set; }
/// <summary>
/// 层级
/// </summary>
int Level { get; set; }
/// <summary>
/// 排序 层级内排序
/// </summary>
int Sort { get; set; }
/// <summary>
/// 父级
/// </summary>
int FatherId { get; set; }
/// <summary>
/// 父级串 替换递归查询用
/// </summary>
string FatherStr { get; set; }
/// <summary>
/// 根级 大块
/// </summary>
int RootId { get; set; }
}
上面得字段,我感觉只有RootId可有可无,其他得还是很重要得,非常重要!!!
按照上面得,相当于把表得结构给确定了,如果你要写RoleInfo,则有
public class RoleInfo:ILevelEntity{
//具体实现 添加其他字段等
}
然后我们把之前RoleInfo的提取出来,用ILevenEntity来作为参数
/// <summary>
/// 多层级 非递归多层级新增和更新
/// </summary>
public class LevelHandler
{
/// <summary>
/// 新增后 注意外部要保存下
/// </summary>
/// <param name="_dbContext"></param>
/// <param name="TableName">应该是PropertyName</param>
/// <param name="info"></param>
public static async Task AfterAdd(IOneCarDbContext _dbContext, string TableName, ILevelEntity info)
{
info.SortStr = info.Sort.ToString().PadLeft(_persize, '0') + "0".PadLeft((_maxlevel - 1) * _persize, '0');
if (info.FatherId <= 0)
{
info.FatherStr = $"{info.Id},";
info.RootId = info.Id;
return;
}
var dbType = typeof(IOneCarDbContext);
var dbProperty = dbType.GetProperty(TableName);
if (dbProperty != null)
{
var dbSet = dbProperty.GetValue(_dbContext) as IQueryable<ILevelEntity>;
if (dbSet != null)
{
var father = await dbSet.Where(x => x.Id == info.FatherId).AsNoTracking().FirstOrDefaultAsync();
if (father != null && father != default)
{
info.FatherStr = $"{father.FatherStr}{info.Id},";
info.Level = (father.Level + 1);
info.FatherId = father.Id;
info.RootId = father.RootId != 0 ? father.RootId : father.Id;
BuildSortStr(info, father);
}
else
{
info.FatherId = 0;
}
}
}
}
/// <summary>
/// 更新后 注意外部要保存下
/// </summary>
/// <param name="_dbContext"></param>
/// <param name="TableName">应该是PropertyName</param>
/// <param name="info"></param>
/// <returns></returns>
public static async Task AfterUpdate(IOneCarDbContext _dbContext, string TableName, ILevelEntity info)
{
IQueryable<ILevelEntity> dbSet = null;
var dbType = typeof(IOneCarDbContext);
var dbProperty = dbType.GetProperty(TableName);
if (dbProperty != null)
{
dbSet = dbProperty.GetValue(_dbContext) as IQueryable<ILevelEntity>;
}
if (dbSet == null)
{
return;
}
var old_father_str = info.FatherStr;
var _chat_level = info.Level;
if (info.FatherId != 0)
{
var father = await dbSet.Where(x => x.Id == info.FatherId).AsNoTracking().FirstOrDefaultAsync();
if (father != null && father != default)
{
info.Level = father.Level + 1;
info.FatherStr = $"{father.FatherStr}{info.Id},";
info.RootId = father.RootId;
BuildSortStr(info, father);
}
else
{
info.FatherId = 0;
info.FatherStr = $"{info.Id},";
info.Level = 0;
info.RootId = info.Id;
BuildSortStr(info, null);
}
}
else
{
info.FatherId = 0;
info.FatherStr = $"{info.Id},";
info.Level = 0;
info.RootId = info.Id;
BuildSortStr(info, null);
}
_chat_level = _chat_level - (info.Level);
if (!String.IsNullOrEmpty(old_father_str))
{
var suns = await dbSet.Where(x => x.Id != info.Id && x.FatherStr.StartsWith(old_father_str)).ToListAsync();
if (suns != null && suns.Count > 0)
{
foreach (var item in suns)
{
item.FatherStr = item.FatherStr.Replace(old_father_str, info.FatherStr);
item.Level = item.Level + _chat_level;
item.RootId = info.RootId;
BuildFootSortStr(item, info);
}
}
}
}
/// <summary>
/// 自动排序 基于同层级排序到后面,只有当当前是0的时候生效
/// </summary>
/// <param name="_dbContext"></param>
/// <param name="TableName">应该是PropertyName</param>
/// <param name="input"></param>
/// <param name="step"></param>
/// <returns></returns>
public static async Task AutoSort(IOneCarDbContext _dbContext, string TableName, ILevelEntity input,int step=10)
{
if (input.Sort == 0)
{
var dbType = typeof(IOneCarDbContext);
var dbProperty = dbType.GetProperty(TableName);
if (dbProperty != null)
{
var dbSet = dbProperty.GetValue(_dbContext) as IQueryable<ILevelEntity>;
if (dbSet != null)
{
var _max = await dbSet.Where(x => x.FatherId == input.FatherId)
.OrderByDescending(x => x.Sort)
.Select(x => x.Sort)
.FirstOrDefaultAsync();
input.Sort = _max + 10;
}
}
}
}
/// <summary>
/// 每层最大支持的排序位数 5表示00000
/// </summary>
private const int _persize = 5;
/// <summary>
/// 最大层级个数,从0开始 比如4就是 0 1 2 3
/// </summary>
private const int _maxlevel = 4;
/// <summary>
/// 基于父级和当前自己的排序信息,构建对应的排序字符串
/// </summary>
/// <param name="_info"></param>
/// <param name="_father"></param>
private static void BuildSortStr(ILevelEntity _info, ILevelEntity _father)
{
if (_info.Level < _maxlevel)
{
if (_father == null)
{
_info.SortStr = _info.Sort.ToString().PadLeft(_persize, '0') + "0".PadLeft(_persize * (_maxlevel - 1), '0');
}
else
{
if (!string.IsNullOrEmpty(_father.SortStr))
{
_info.SortStr = _father.SortStr.Substring(0, (_father.Level + 1) * _persize) + _info.Sort.ToString().PadLeft(_persize, '0');
//补充尾巴
if (_info.Level < (_maxlevel - 1))
{
_info.SortStr += "0".PadLeft(_persize * (_maxlevel - 1 - _info.Level), '0');
}
}
}
}
}
/// <summary>
/// 上级或者祖籍变更的替换,自己之后的不变,中间层级也不变
/// </summary>
/// <param name="_info"></param>
/// <param name="_father">是自己的上级或者祖级</param>
private static void BuildFootSortStr(ILevelEntity _info, ILevelEntity _father)
{
var _old_sort_str = _info.SortStr;
_info.SortStr = _father.SortStr.Substring(0, (_father.Level + 1) * _persize);
_info.SortStr += _old_sort_str.Substring((_father.Level + 1) * _persize);
}
}
上面代码,其实其他都不用看,注意2个参数
/// <summary>
/// 每层最大支持的排序位数 5表示00000
/// </summary>
private const int _persize = 5;
/// <summary>
/// 最大层级个数,从0开始 比如4就是 0 1 2 3
/// </summary>
private const int _maxlevel = 4;
第一个参数_persize表示当层的最大排序,所以设置排序的时候要注意这个问题,目前上面的代码没有判断,是不完善的!!!
其实我感觉我们平时用的话3位数就够了!
你想啊,同一级有999个对象,多层级,已经是很大的数据了!
和_maxlevel组合后,就是sortstr字段的最大长度了!
那么问题来了,如何调用?
/// <summary>
/// 这是一个新增案例
///</summary>
/// <param name="input"></param>
/// <returns></returns>
public static async Task<RoleInfoDto> RoleAdd(IOneCarDbContext _dbContext, IObjectMapper ObjectMapper, RoleInfoAddDto input)
{
if (input.Sort == 0)
{
var _max = await _dbContext.RoleInfo.Where(x => x.FatherId == input.FatherId)
.OrderByDescending(x => x.Sort)
.Select(x => x.Sort)
.FirstOrDefaultAsync();
input.Sort = _max + 10;
}
if (input.RoleType == 0)
{
if (String.IsNullOrEmpty(input.Model) || String.IsNullOrEmpty(input.Role))
{
throw new PasteCodeException("新增权限的时候,模块和权限必填,请重新填写!");
}
var find = await _dbContext.RoleInfo.Where(x => x.RoleType == 0 && x.Model == input.Model && x.Role == input.Role).Select(x => x.Id).FirstOrDefaultAsync();
if (find > 0)
{
throw new PasteCodeException("已经有这个菜单项了,请勿重复添加!");
}
}
if (input.RoleType == enummodels.EnumRoleType.Menu)
{
if (!string.IsNullOrEmpty(input.Path))
{
if (input.FatherId == 0)
{
throw new PasteCodeException("非一级菜单,父级必选,请重新选择!");
}
var find = await _dbContext.RoleInfo.Where(x => x.RoleType == enummodels.EnumRoleType.Menu && x.Path == input.Path).Select(x => x.Id).FirstOrDefaultAsync();
if (find > 0)
{
throw new PasteCodeException("已经有这个菜单项了,请勿重复添加!");
}
}
}
//这里最好用事务包裹下
var info = ObjectMapper.Map<RoleInfoAddDto, RoleInfo>(input);
info.IsEnable = true;//添加自定义
_dbContext.Add(info);
await _dbContext.SaveChangesAsync();
await AfterAdd(_dbContext, nameof(RoleInfo), info);
await _dbContext.SaveChangesAsync();
var backinfo = ObjectMapper.Map<RoleInfo, RoleInfoDto>(info);
return backinfo;
}
/// <summary>
/// 这是一个更新案例
///</summary>
/// <param name="input"></param>
/// <returns></returns>
public static async Task<RoleInfoDto> RoleUpdate(IOneCarDbContext _dbContext, IObjectMapper ObjectMapper, RoleInfoUpdateDto input)
{
if (input.Sort == 0)
{
var _max = await _dbContext.RoleInfo.Where(x => x.FatherId == input.FatherId)
.OrderByDescending(x => x.Sort)
.Select(x => x.Sort)
.FirstOrDefaultAsync();
input.Sort = _max + 10;
}
if (input.RoleType == 0)
{
if (String.IsNullOrEmpty(input.Model) || String.IsNullOrEmpty(input.Role))
{
throw new PasteCodeException("新增权限的时候,模块和权限必填,请重新填写!");
}
var find = await _dbContext.RoleInfo.Where(x => x.RoleType == 0 && x.Model == input.Model && x.Role == input.Role && x.Id != input.Id).Select(x => x.Id).FirstOrDefaultAsync();
if (find > 0)
{
throw new PasteCodeException("已经有这个菜单项了,请勿重复添加!");
}
}
if (input.RoleType == enummodels.EnumRoleType.Menu)
{
if (!string.IsNullOrEmpty(input.Path))
{
if (input.FatherId == 0)
{
throw new PasteCodeException("非一级菜单,父级必选,请重新选择!");
}
var find = await _dbContext.RoleInfo.Where(x => x.RoleType == enummodels.EnumRoleType.Menu && x.Path == input.Path && x.Id != input.Id).Select(x => x.Id).FirstOrDefaultAsync();
if (find > 0)
{
throw new PasteCodeException("已经有这个菜单项了,请勿重复添加!");
}
}
}
var info = await _dbContext.RoleInfo.Where(x => x.Id == input.Id).FirstOrDefaultAsync();
if (info == null || info == default)
{
throw new PasteCodeException("需要查询的信息不存在");
}
ObjectMapper.Map<RoleInfoUpdateDto, RoleInfo>(input, info);
await AfterUpdate(_dbContext, nameof(RoleInfo), info);
var backinfo = ObjectMapper.Map<RoleInfo, RoleInfoDto>(info);
await _dbContext.SaveChangesAsync();
return backinfo;
}
比如我再另外一个表使用的
/// <summary>
/// 添加一个
///</summary>
/// <param name="input"></param>
/// <returns></returns>
[HttpPost]
[TypeFilter(typeof(RoleAttribute), Arguments = new object[] { "data", "add" })]
public async Task<string> CreateItemAsync(CateInfoAddDto input)
{
var _userid = base.ReadCurrentUserId();
var info = ObjectMapper.Map<CateInfoAddDto, CateInfo>(input);
await LevelHandler.AutoSort(_dbContext, nameof(CateInfo), info);
_dbContext.Add(info);
await _dbContext.SaveChangesAsync();
await LevelHandler.AfterAdd(_dbContext, nameof(CateInfo), info);
await _dbContext.SaveChangesAsync();
//这里最好用事务包裹下,因为会保存2次
return "新增成功";
}
/// <summary>
/// 更新一个
///</summary>
/// <param name="input"></param>
/// <returns></returns>
[HttpPost]
[TypeFilter(typeof(RoleAttribute), Arguments = new object[] { "data", "edit" })]
public async Task<string> UpdateItemAsync(CateInfoUpdateDto input)
{
var info = await _dbContext.CateInfo.Where(x => x.Id == input.Id).FirstOrDefaultAsync();
if (info == null || info == default)
{
throw new PasteCodeException("需要查询的信息不存在", 404);
}
ObjectMapper.Map<CateInfoUpdateDto, CateInfo>(input, info);
await LevelHandler.AutoSort(_dbContext, nameof(CateInfo), info);
await LevelHandler.AfterUpdate(_dbContext, nameof(CateInfo), info);
await _dbContext.SaveChangesAsync();
return "修改成功";
}
是不是很方便了?
赶紧试试吧!