Skip to content

Instantly share code, notes, and snippets.

@zhaopan
Last active March 1, 2023 07:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save zhaopan/8aac1993a1a61b6b50ecc69e563e32af to your computer and use it in GitHub Desktop.
Save zhaopan/8aac1993a1a61b6b50ecc69e563e32af to your computer and use it in GitHub Desktop.
EnumPower 角色权限
using System;
using System.Collections;
using System.Text;
namespace Common.Lib
{
/// <summary>
/// 角色权限对象,拥有指定权限。(可支持64个独立的权限项)
/// </summary>
/// <typeparam name="T">泛型 T (权限列表)必须是一个枚举。</typeparam>
/// <remarks>
/// <para>此模块基于二进制操作编写。</para>
/// <para>要使用此模块,只需要定义一个枚举即可,这个枚举即表示了各个独立的权限项。但是这个枚举必须满足以下条件:
/// </para>
/// <ol>
/// <li>以ulong为基类。</li>
/// <li>枚举值必须为2的正整数次方或1。</li>
/// <li>枚举项不超过 64 项。</li>
/// </ol>
/// </remarks>
/// <example>
/// <para>
/// 例如下面这个枚举就是一个合法的权限枚举:
/// </para>
/// <code>
/// [Flags]
/// public enum PowerItemExample : ulong
/// {
/// 查看用户 = 1,
/// 增改用户 = 2,
/// 删除用户 = 4,
///
/// 查看系统日志 = 8,
/// 管理其他管理员 = 16,
/// 关闭网站 = 32
/// }
/// </code>
/// <para>
/// <strong>* 需特别注意:必须显式地为枚举项提供值,否则若有任何枚举项值为 0,就不符合权限枚举的前提条件了。</strong></para>
/// <para>
/// 如上所示,在这个权限枚举 <typeparamref name="T"/> 上声明上了 <see cref="System.FlagsAttribute"/> 属性,以便使权限项支持按位操作。<br />
/// 按位操作时请务必注意逻辑关系。例如,同时拥有“查看用户”和“增改用户”的权限:
/// <ul>
/// <li><![CDATA[正确的:PowerItemExample.查看用户 | PowerItemExample.增改用户。]]></li>
/// <li><![CDATA[错误的:PowerItemExample.查看用户 & PowerItemExample.增改用户。(此表达式将恒等于 0,即无权限。)]]></li>
/// </ul>
/// 而要表示拥有“查看用户”或“增改用户”的权限时,只能分开表示这两个权限。在这一点上请尤为注意。
/// </para>
/// <para>
/// 权限枚举 <typeparamref name="T"/> 在进行按位操作之后所得到的返回值,仍然是 <typeparamref name="T"/> 类型。
/// </para>
/// <para></para>
/// <para>有了上面的枚举项,我们就可以使用这个模块进行权限相关的操作。如:</para>
///
/// <para><strong>建立一个无权限的角色对象:</strong></para>
/// <code><![CDATA[
/// EnumPower<PowerItemExample> m = new PoweredMember<PowerItemExample>();
/// ]]></code>
///
/// <para><strong>建立拥有“查看用户”和“增改用户”权限的角色对象:</strong></para>
/// <code><![CDATA[
/// EnumPower<PowerItemExample> member = new PoweredMember<PowerItemExample>(PowerItemExample.查看用户 | PowerItemExample.增改用户);
/// ]]></code>
///
/// <para><strong>为此角色对象赋予“查看用户”和“查看系统日志”的权限:</strong></para>
/// <code><![CDATA[
/// // 此操作将在原有的权限基础上赋予新的权限。member的旧权限为“查看用户、增改用户”,现在的新权限为“查看用户、增改用户、查看系统日志”。
/// // 即,在赋予权限时,将会把新的权限添加进去,而不会理会旧的权限。
/// member.Append(PowerItemExample.查看用户 | PowerItemExample.查看系统日志);
/// ]]></code>
///
/// <para><strong>剥夺此角色对象“删除用户”和“增改用户”的权限:</strong></para>
/// <code><![CDATA[
/// // 此操作将从原有的权限中除去指定的权限。member的旧权限为“查看用户、增改用户、查看系统日志”,现在的新权限为“查看用户、查看系统日志”。
/// // 即,在剥夺权限时,将会把指定的权限从原有权限中除去,如果原来本没用某种权限,将不会理睬。
/// member.Remove(PowerItemExample.删除用户 | PowerItemExample.增改用户);
/// ]]></code>
///
/// <para><strong>检查角色对象是否具有指定的权限:</strong></para>
/// <code><![CDATA[
/// member.Check(PowerItemExample.删除para用户); // false
/// member.Check(PowerItemExample.查看用户); // true
/// member.Check(PowerItemExample.查看系统日志); // true
/// member.Check(PowerItemExample.查看用户 | PowerItemExample.查看系统日志); // true
/// ]]></code>
///
/// <para>为方便调试输出,此模块重写了ToString方法用于输出权限细节。例如输出上文最后状态的 member.ToString(),则会有:</para>
/// <code>
/// 角色权限
/// -------------------------------------
/// 权限标识(ulong):9
/// 二进制字符串:1001
/// 权限详情:(查看用户, 查看系统日志)
/// 允许:查看用户
/// 禁止:增改用户
/// 禁止:删除用户
/// 允许:查看系统日志
/// 禁止:管理其他管理员
/// 禁止:关闭网站
/// </code>
/// </example>
/// <exception cref="ArgumentException">泛型 T (权限列表)必须是一个枚举!</exception>
/// <exception cref="ArgumentException">泛型 T (权限列表)所指定的枚举其基础类型必须是 ulong !</exception>
/// <exception cref="ArgumentException">泛型 T (权限列表)的值必须是2的正整数次方或1,且不能重复!</exception>
/// <exception cref="ArgumentOutOfRangeException">参数必须大于 -1 !</exception>
/// <exception cref="ArgumentOutOfRangeException">参数必须是由0、1构成的二进制字符串!</exception>
public class EnumPower<T> where T : IComparable, IConvertible, IFormattable
{
#region CONST
private const string TTypeErr = "泛型 T (权限列表)必须是一个枚举!";
private const string TBaseErr = "泛型 T (权限列表)所指定的枚举其基础类型必须是 ulong !";
private const string TValueErr = "泛型 T (权限列表)的值必须是2的正整数次方或1,且不能重复!";
private const string MinPowerErr = "参数必须大于 -1 !";
private const string PowerStringErr = "参数必须是由0、1构成的二进制字符串!";
#endregion CONST
#region Properties
private T _power;
/// <summary>
/// 获取当前对象的权限
/// </summary>
public T Power
{
get => _power;
private set
{
#region 检查枚举值的合法性
Array values = Enum.GetValues(value.GetType());
ulong tmpV = 0;
//检查泛型的每一个值的合法性
for (int n = 0; n < values.Length; n++)
{
tmpV = TtoULong((T)values.GetValue(n));
//检查是否为2的正整数次方或1
if (!IsPowerOfTwo(tmpV) && tmpV != 1) { throw new ArgumentException(TValueErr); }
//检查当前值是否重复
for (int k = 0; k < n; k++)
{
if (tmpV == TtoULong((T)values.GetValue(k)))
{
throw new ArgumentException(TValueErr);
}
}
}
#endregion 检查枚举值的合法性
_power = value;
}
}
//检查是否为2的正整数次方
private static bool IsPowerOfTwo(ulong value)
{
if (value > 1)
{
/*
* 针对每一个 ulong 的正整数,如果是2的正整数次方,则转换为二进制表示时一定有且仅有一个“1”存在。
* 基于以上原理,则可以按位来比较。
* 从低位到高位查询,遇到“1”后停止比较,所得到的数应当与原数相等,
* 否则该数的二进制形式不止一个“1”位,即该数一定不是2的正整数次方。
*/
ulong mask = 1ul;
for (int count = 0; count < 64; count++)
{
//检查当前标识位是否为“0”
if ((mask & value) == 0)
{
//当前位为“0”,则左移标识位,再次检查
mask <<= 1;
continue;
}
/*
* 程序执行到此,表明当前标识位为“1”,停止检查。
* 而此时的 mask 值正好应当与 value 相等,否则 value 就不是2的正整数次方。
*/
return mask == value;
}
}
return false;
}
/// <summary>
/// 获取当前对象权限的二进制字符串形式
/// </summary>
public string PowerString
{
get
{
ulong v = TtoULong(Power);
return UlongToStr(v);
}
}
/// <summary>
/// 获取当前对象权限的长整形形式
/// </summary>
public ulong PowerUInt64 => TtoULong(Power);
#endregion Properties
#region ctor
/// <summary>
/// 构造一个角色权限对象,无任何权限。
/// </summary>
public EnumPower()
{
if (Enum.GetUnderlyingType(typeof(T)) != typeof(ulong)) { throw new ArgumentException(TBaseErr); }
T tmp = (T)Enum.Parse(typeof(T), 0.ToString());
Power = tmp;
}
/// <summary>
/// 构造一个角色权限对象,权限由权限枚举指定。
/// </summary>
/// <param name="ownedPower">该角色拥有的权限。如:“<typeparamref name="T"/>.查看用户 | <typeparamref name="T"/>.增改用户”</param>
public EnumPower(T ownedPower)
{
if (!(ownedPower.GetType().IsEnum))
{
throw new ArgumentException(TTypeErr);
}
if (Enum.GetUnderlyingType(typeof(T)) != typeof(ulong)) { throw new ArgumentException(TBaseErr); }
Power = ownedPower;
}
/// <summary>
/// 构造一个角色权限对象,权限由权限标识指定(标识将被转换为二进制形式)。
/// </summary>
/// <param name="ownedPower">该角色拥有的权限(标识,将被转换为二进制形式)</param>
public EnumPower(ulong ownedPower)
{
if (ownedPower < 0) { throw new ArgumentOutOfRangeException(MinPowerErr); }
if (Enum.GetUnderlyingType(typeof(T)) != typeof(ulong)) { throw new ArgumentException(TBaseErr); }
T tmp = (T)Enum.Parse(typeof(T), ownedPower.ToString());
Power = tmp;
}
/// <summary>
/// 构造一个角色权限对象,权限由二进制字符串指定。
/// </summary>
/// <param name="ownedPower">该角色拥有的权限(二进制如:“10000100000”)</param>
public EnumPower(string ownedPower)
{
ulong p = 0UL;
if (!string.IsNullOrEmpty(ownedPower) && ownedPower.Trim().Length > 0)
{
if (ownedPower.Trim().Replace("0", "").Replace("1", "").Length != 0)
{
throw new ArgumentOutOfRangeException(PowerStringErr);
}
p = Convert.ToUInt64(ownedPower, 2);
}
T tmp = (T)Enum.Parse(typeof(T), p.ToString());
Power = tmp;
}
#endregion ctor
#region public
/// <summary>
/// 检查当前对象是否满足指定权限要求
/// </summary>
/// <param name="powerNeeded">需要满足的权限</param>
/// <returns></returns>
public bool Check(T powerNeeded)
{
return Check(Power, powerNeeded);
}
/// <summary>
/// 检查当前对象是否满足指定权限要求.
/// </summary>
/// <param name="power">权限集合.</param>
/// <param name="powerNeeded">需要满足的权限</param>
/// <remarks>
/// True:满足权限.反之不满足.
/// </remarks>
/// <returns></returns>
public static bool Check(T power, T powerNeeded)
{
//if (!Enum.IsDefined(typeof(T), powerNeeded))
// throw new ArgumentOutOfRangeException("powerNeeded", "枚举值 “" + powerNeeded.ToString() + "” 未定义!");
if (power.ToString() == "0") { return false; }
if (powerNeeded.ToString() == "0") { return false; }
ulong uPower = TtoULong(power);
ulong uPowerNeeded = TtoULong(powerNeeded);
//用户拥有的权限: 10110
//操作需要的权限: 10010
//& 操作 -------------
// 10010
// = 操作需要的权限
return ((uPower & uPowerNeeded) == uPowerNeeded);
}
/// <summary>
/// 给现有角色对象赋予一个权限
/// </summary>
/// <param name="power">要赋予的权限</param>
public void Append(T power)
{
ulong n = TtoULong(_power);
ulong myPower = TtoULong(power);
_power = (T)Enum.Parse(typeof(T), (n | myPower).ToString());
}
/// <summary>
/// 从现有角色对象剥夺一个权限
/// </summary>
/// <param name="power"></param>
public void Remove(T power)
{
ulong n = TtoULong(_power);
ulong myPower = TtoULong(power);
_power = (T)Enum.Parse(typeof(T), (n ^ (n & myPower)).ToString());
}
/// <summary>
/// 查看当前对象的权限详情
/// </summary>
/// <returns></returns>
public override string ToString()
{
StringBuilder sb = new StringBuilder();
sb.AppendLine("角色权限");
sb.AppendLine("-------------------------------------");
sb.AppendLine("权限标识(ulong):" + TtoULong(Power));
sb.AppendLine("二进制字符串:" + PowerString);
sb.AppendLine("权限详情:(" + Power + ")");
Array powers = Enum.GetValues(typeof(T));
IEnumerator ie = powers.GetEnumerator();
T tmpP;
while (ie.MoveNext())
{
tmpP = (T)Enum.Parse(typeof(T), ie.Current.ToString());
if (Check(tmpP))
{
sb.AppendLine("\t允许:" + tmpP.ToString());
}
else
{
sb.AppendLine("\t禁止:" + tmpP.ToString());
}
}
return sb.ToString();
}
#endregion public
#region private
/// <summary>
/// 将64位无符号整数(ulong)转换为二进制字符串
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
private string UlongToStr(ulong value)
{
int size = 64;
ulong mask = 1ul << size - 1;
StringBuilder sb = new StringBuilder();
string result = string.Empty;
for (int count = 0; count < size; count++)
{
result = ((mask & value) > 0) ? "1" : "0";
sb.Append(result);
// Shift mask one location over to the right
mask >>= 1;
}
return sb.ToString().TrimStart('0');
}
/// <summary>
/// 将权限对象转化为长整形形式
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
private static ulong TtoULong(T obj)
{
return (ulong)Enum.Parse(typeof(T), obj.ToString());
}
#endregion private
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment