因为项目中遇到了审核分配的需求,而且可以预见的是后面这种类似的分配需求还会出现,所以就有了这样一个小类库的产生
首先先说明下需求原因:现在有审核人员,有待审核的订单,因为审核人员的奖金跟审核过的订单金额同比挂钩(即审的订单总额越多,奖金越高,也可能跟绩效挂钩),造成审核人员只抢大单,小单子长时间无人审,对外影响恶劣,为了解决这个问题,以及为后续的类似问题提供一个通用的解决方案,故而设计了这么一个小类库
首先对应各部分存在三个接口:
1、待分配的用户集合,该接口定义了用户相关的各种行为,用户是否能进行分配取决于用户是否登入
/// <summary>
/// 自动分配用户
/// </summary>
public interface IAllotUsers<T>
{
/// <summary>
/// 登入
/// </summary>
/// <param name="user"></param>
/// <returns></returns>
bool LoginIn(T user);
/// <summary>
/// 登出
/// </summary>
/// <param name="user"></param>
/// <returns></returns>
bool LoginOut(T user);
/// <summary>
/// 验证是否已登入
/// </summary>
/// <param name="user"></param>
/// <returns></returns>
bool IsLogin(T user);
/// <summary>
/// 获取所有已登入用户
/// </summary>
/// <returns></returns>
IEnumerable<T> GetAllLoginUsers();
}
2、待分配项的集合,该接口定义了分配相关的各种行为,此部分重点需要注意的是Add方法和Get方法:Add方法存在一个无参但返回int的委托,该委托指示分配的先后顺序,Get方法则是对遍历时的分配项进行是否可分配判定,返回true标志可以分配
/// <summary>
/// 自动分配项
/// </summary>
public interface IAllotItems<T>
{
/// <summary>
/// 添加可分配项
/// </summary>
/// <param name="item">可分配项</param>
/// <param name="fun">添加规则(影响Get方法的顺序),如果传入null,则默认为第一分配队列,如果超出允许的队列范围,则修正为最后一个队列</param>
void Add(T item, Func<int> fun);
/// <summary>
/// 获取可分配项
/// </summary>
/// <param name="fun">分配规则</param>
/// <param name="item">分配到的分配项</param>
/// <returns>是否分配成功</returns>
bool Get(Func<T, bool> fun, ref T item);
/// <summary>
/// 取消获取
/// </summary>
/// <param name="item"></param>
/// <returns></returns>
bool GetBack(T item);
/// <summary>
/// 设定指定分配项已经处于分配状态
/// </summary>
/// <param name="item"></param>
void Set(T item);
/// <summary>
/// 移除可分配项
/// </summary>
/// <param name="item">可分配项</param>
/// <returns>返回移除是否成功</returns>
bool Remove(T item);
/// <summary>
/// 获取所有可分配项
/// </summary>
/// <returns></returns>
IEnumerable<T> GetAllAllotItems();
}
3、分配关系,该类库保持了分配用户与分配项之间的分配关系,需要指明的是该类库继承了IAllotUsers<Tu>, IAllotItems<Ti>
/// <summary>
/// 自动分配
/// </summary>
public interface IAutoAllot<Tu, Ti> : IAllotUsers<Tu>, IAllotItems<Ti>
{
/// <summary>
/// 是否已经存在分配记录
/// </summary>
/// <param name="user"></param>
/// <returns></returns>
bool IsAllot(Tu user);
/// <summary>
/// 获取可分配项,如存在分配记录,则直接返回记录对应的分配项目
/// </summary>
/// <typeparam name="T1">待分配者类型</typeparam>
/// <param name="user">待分配者</param>
/// <param name="fun">分配规则</param>
/// <param name="item">分配到的分配项</param>
/// <returns>是否分配成功</returns>
bool GetAllotItem(Tu user, Func<Ti, bool> fun, ref Ti item);
/// <summary>
/// 撤销分配
/// </summary>
/// <param name="user"></param>
/// <returns></returns>
bool GetBackAllotItem(Tu user);
/// <summary>
/// 分配结束,移除分配关系以及可分配项
/// </summary>
/// <param name="user"></param>
/// <returns></returns>
bool Finished(Tu user);
}
然后针对接口定义,实现了相应的默认的分配类
1、GeneralAllotUsers,该类库内部包含一个HashSet作为集合载体,保证每个用户在分配队列中的唯一性
/// <summary>
/// 通用分配用户
/// </summary>
/// <typeparam name="T"></typeparam>
public class GeneralAllotUsers<T> : IAllotUsers<T>
{
private HashSet<T> _users = new HashSet<T>();
#region IAllotUsers<T> 成员
public bool LoginIn(T user)
{
bool ret = true;
if (!this._users.Contains(user))
{
ret = this._users.Add(user);
}
return ret;
}
public bool LoginOut(T user)
{
bool ret = true;
if (this._users.Contains(user))
{
ret = this._users.Remove(user);
}
return ret;
}
public bool IsLogin(T user)
{
return this._users.Contains(user);
}
public IEnumerable<T> GetAllLoginUsers()
{
return this._users.ToArray();
}
#endregion
}
2、GeneralAllotItems,该类库内部包含一个Dictionary来作为待分配项的载体,Key对应待分配项,Value对应该分配项是否已经被分配(分配项不允许被重复分配),然后通过List<List<T>>来作为分配顺序的一个载体,另外通过一个private的object对象来保证该类在多线程时的分配唯一性
/// <summary>
/// 通用分配项
/// </summary>
/// <typeparam name="T"></typeparam>
public class GeneralAllotItems<T> : IAllotItems<T>
{
private Dictionary<T, bool> _dic = new Dictionary<T, bool>();
private List<List<T>> _list = new List<List<T>>();
private object _obj = new object();
public GeneralAllotItems()
: this(1)
{
}
/// <summary>
/// 分配初始化
/// </summary>
/// <param name="levels">存在多少个分配队列</param>
public GeneralAllotItems(int levels)
{
if (levels <= 0)
{
throw new ArgumentException();
}
for (int i = 0; i < levels; i++)
{
this._list.Add(new List<T>());
}
}
#region IAllotItems<T> 成员
public void Add(T item, Func<int> fun)
{
if (!this._dic.ContainsKey(item))
{
int lv = 0;
if (fun != null)
{
lv = fun();
}
//修正lv的值范围
if (lv < 0)
{
lv = 0;
}
else if (lv >= this._list.Count)
{
lv = this._list.Count - 1;
}
this._list[lv].Add(item);
this._dic.Add(item, false);
}
}
public bool Get(Func<T, bool> fun, ref T item)
{
foreach (var l in this._list)
{
foreach (T k in l)
{
if (!this._dic[k])//如果该分配项尚未分配过
{
lock (this._obj)
{
if (!this._dic[k])
{
if (fun == null || fun(k))//不存在分配规则或者符合分配规则
{
item = k;
this._dic[k] = true;
return true;
}
}
}
}
}
}
return false;
}
public bool GetBack(T item)
{
bool ret = false;
if (this._dic.ContainsKey(item))
{
ret = true;
this._dic[item] = false;
}
return ret;
}
public void Set(T item)
{
if (this._dic.ContainsKey(item))
{
this._dic[item] = true;
}
}
public bool Remove(T item)
{
for (int i = 0; i < this._list.Count; i++)
{
if (this._list[i].Contains(item))
{
this._list[i].Remove(item);
break;
}
}
return this._dic.Remove(item);
}
public IEnumerable<T> GetAllAllotItems()
{
return (from l in this._list
let p = l
from t in p
select t).ToArray();
}
#endregion
}
3、GeneralAutoAllot,该类库存在三个构造方法,传入的Dictionary是为了保证一旦程序异常,可以恢复分配关系,至于传入的IAllotUsers<Tu> users, IAllotItems<Ti> items则是允许传入自定义的分配方式
/// <summary>
/// 自动分配通用类
/// </summary>
/// <typeparam name="Tu"></typeparam>
/// <typeparam name="Ti"></typeparam>
public class GeneralAutoAllot<Tu, Ti> : IAutoAllot<Tu, Ti>
{
private IAllotItems<Ti> _items;
private IAllotUsers<Tu> _users;
private Dictionary<Tu, Ti> _dic;
public GeneralAutoAllot()
: this(null)
{
}
public GeneralAutoAllot(Dictionary<Tu, Ti> dic)
: this(dic, null, null)
{
}
public GeneralAutoAllot(Dictionary<Tu, Ti> dic, IAllotUsers<Tu> users, IAllotItems<Ti> items)
{
this._dic = dic;
if (this._dic == null)
{
this._dic = new Dictionary<Tu, Ti>();
}
this._users = users;
if (this._users == null)
{
this._users = new GeneralAllotUsers<Tu>();
}
this._items = items;
if (this._items == null)
{
this._items = new GeneralAllotItems<Ti>();
}
}
#region IAutoAllot<Tu,Ti> 成员
public bool IsAllot(Tu user)
{
return this._dic.ContainsKey(user);
}
/// <summary>
/// FIFO,先入先出
/// </summary>
/// <param name="user"></param>
/// <param name="fun"></param>
/// <param name="item"></param>
/// <returns></returns>
public bool GetAllotItem(Tu user, Func<Ti, bool> fun, ref Ti item)
{
bool ret = false;
if (this._dic.ContainsKey(user))
{//已经有分配记录,则直接获取分配记录中对应的分配项
ret = true;
item = this._dic[user];
}
else
{
//是否已经登入
if (this._users.IsLogin(user))
{
//从items中获取可分配项
ret = this._items.Get(fun, ref item);
if (ret)
{//成功分配后记录分配关系
this._dic.Add(user, item);
}
}
}
return ret;
}
public bool GetBackAllotItem(Tu user)
{
bool ret = false;
if (this._dic.ContainsKey(user))
{
ret = this._items.GetBack(this._dic[user]);
this._dic.Remove(user);
}
return ret;
}
public bool Finished(Tu user)
{
bool ret = false;
if (this._dic.ContainsKey(user))
{
ret = this._items.Remove(this._dic[user]);
this._dic.Remove(user);
}
return ret;
}
public IEnumerable<Tu> GetAllLoginUsers()
{
return this._users.GetAllLoginUsers();
}
#endregion
#region IAllotUsers<Tu> 成员
public bool LoginIn(Tu user)
{
return _users.LoginIn(user);
}
public bool LoginOut(Tu user)
{
return _users.LoginOut(user);
}
public bool IsLogin(Tu user)
{
return _users.IsLogin(user);
}
#endregion
#region IAllotItems<Ti> 成员
public void Add(Ti item, Func<int> fun)
{
_items.Add(item, fun);
}
public bool Get(Func<Ti, bool> fun, ref Ti item)
{
return _items.Get(fun, ref item);
}
public bool GetBack(Ti item)
{
return _items.GetBack(item);
}
public void Set(Ti item)
{
_items.Set(item);
}
public bool Remove(Ti item)
{
return _items.Remove(item);
}
public IEnumerable<Ti> GetAllAllotItems()
{
return this._items.GetAllAllotItems();
}
#endregion
}
至于用法就是简单的实现一个分配的代理类,该代理类内部静态化一个IAutoAllot对象(其实就是在这个代理类内部只能有一个实例化后的IAutoAllot对象)
public class AutoAllotProxy
{
/// <summary>
/// 自动分配类
/// </summary>
private static IAutoAllot<string, int> AutoAllot;
static AutoAllotProxy()
{
InitData(null);
}
/// <summary>
/// 政策待分配数据初始化
/// </summary>
private static void InitData(IEnumerable<string> loginUsers)
{
//数据恢复
List<KeyValuePair<int, string>> list;//list=XXXXXX此部分读取数据库
//从读取出来的数据中恢复分配关系
Dictionary<string, int> tmpDic = new Dictionary<string, int>();
HashSet<int> alloting = new HashSet<int>();//正在审核
List<int> allot = new List<int>();//待审核政策,需要按时间排序
if (list != null && list.Count > 0)
{
//为了防止出现单个用户存在多个正在审核记录,采用foreach遍历
foreach (var k in list)
{
if (!string.IsNullOrEmpty(k.Value))
{
//如果单个用户有多条审核记录,则只接受第一条审核关系,其余审核关系被排除
if (!tmpDic.ContainsKey(k.Value))
{
tmpDic.Add(k.Value, k.Key);
alloting.Add(k.Key);
allot.Add(k.Key);
}
}
else
{
allot.Add(k.Key);
}
}
}
//恢复用户和政策的分配关系
AutoAllot = new GeneralAutoAllot<string, int>(tmpDic);
foreach (var k in allot)
{//恢复政策列表
AutoAllot.Add(k, null);
}
foreach (var k in alloting)
{//恢复政策分配状态
AutoAllot.Set(k);
}
var users = loginUsers;
if (users == null || !users.Any())
{
users = tmpDic.Keys;
}
foreach (var k in users)
{//恢复用户登入状态
AutoAllot.LoginIn(k);
}
}
/// <summary>
/// 用户登入
/// </summary>
/// <param name="userName"></param>
public void LoginIn(string userName)
{
if (!string.IsNullOrEmpty(userName))
{
AutoAllot.LoginIn(userName);
}
}
/// <summary>
/// 用户退出
/// </summary>
/// <param name="userName"></param>
/// <returns>0:失败,1:成功,2:退出但存在分配记录</returns>
public int LoginOut(string userName)
{
int ret = 0;
if (!string.IsNullOrEmpty(userName))
{
ret = AutoAllot.LoginOut(userName) ? 1 : 0;
if (ret == 1 && AutoAllot.IsAllot(userName))
{
ret = 2;
}
}
return ret;
}
/// <summary>
/// 用户是否已经登入
/// </summary>
/// <param name="userName"></param>
/// <returns></returns>
public bool IsLogin(string userName)
{
if (!string.IsNullOrEmpty(userName))
{
return AutoAllot.IsLogin(userName);
}
return true;
}
/// <summary>
/// 添加待审核项
/// </summary>
/// <param name="pid"></param>
public void Add(int pid)
{
if (pid > 0)
{
AutoAllot.Add(pid, null);//因为分配顺序没有特殊要求,只是简单的先入先出,所以直接传入null
}
}
/// <summary>
/// 获取分配的待审核项,该方法只负责分配,将分配关系持久化到数据库的事情由外部来实现
/// </summary>
/// <param name="userName"></param>
/// <returns></returns>
public int Get(string userName)
{
int pid = 0;
if (!string.IsNullOrEmpty(userName))
{
AutoAllot.GetAllotItem(userName,
null,
ref pid);
}
return pid;
}
/// <summary>
/// 政策审核结束
/// </summary>
/// <param name="userName"></param>
/// <returns></returns>
public bool Finished(string userName)
{
if (!string.IsNullOrEmpty(userName))
{
return AutoAllot.Finished(userName);
}
return false;
}
/// <summary>
/// 获取当前内存中存在的分配项
/// </summary>
/// <returns></returns>
public int[] GetAllAllotPolicies()
{
return AutoAllot.GetAllAllotItems().ToArray();
}
/// <summary>
/// 获取所有登入的用户
/// </summary>
/// <returns></returns>
public string[] GetAllLoginUsers()
{
return AutoAllot.GetAllLoginUsers().ToArray();
}
/// <summary>
/// 分配重新开启
/// </summary>
public void ResetAllot()
{
InitData(AutoAllot.GetAllLoginUsers());//将当前已登录的用户传入进行重新初始化分配关系
}
}
需要特别说明的是,该类库设计之初就没考虑过跟数据库进行交互,所有的数据都存在于内存中,所以受服务重启影响较大,因此在设计时也就提供了相关的初始化入口,以保证数据恢复功能