- 需求
将本地公司部门信息更新到微信企业号通讯录(可改进为定期更新)
- 说明
保存在本地数据库的公司部门信息,采用类似如下格式保存:
UCName | UCID | ParentUCID |
A | 1001000 | |
B | 1002000 | |
A1 | 1001100 | 1001000 |
A2 | 1001200 | 1001000 |
A11 | 1001110 | 1001100 |
...... |
由于UCID为字符串格式,而通讯录保存的公司部门ID为int类型,所以上传到企业号通讯录的公司部门信息会形成新的ID。这就形成了同一部门在本地、通讯录各有各的ID、父ID。
- 解决方案
为保证部门结构关系同步准确,对部门数据进行树形结构化,由于部门信息,部门名可能存在同名,但同一部门下不存在同名的子部门,所以对各节点进行部门名、父节点关系进行匹对,以确定两棵部门树最终一致(ID和父ID不同)。
同时建立一“公司-通讯录”部门映射表。
原始本地『公司部门树』如下:
上传后的『通讯录部门树』如下:
『公司—通讯录部门映射表』数据:
UCName | DPID | ParentDPID | UCID | ParentUCID |
A | 2 | 1 | 1001000 | |
B | 3 | 1 | 1002000 | |
A1 | 4 | 2 | 1001100 | 1001000 |
A2 | 5 | 2 | 1001200 | 1001000 |
A11 | 6 | 4 | 1001110 | 1001100 |
树结构:
【部门数据变化】
当本地公司部门信息改变后,本地部门树如下:
新增部门数(橘红色):4
更新部门数(橘黄色):1(含1子部门)
由于有新部门父节点也是新增部门,为保证父子部门关系正确,对新增部门的同步化采用:先添加新增部门,再确定父部门编号。
将公司部门表与映射表进行匹对,向映射表添加新的部门信息,形成如下树结构:
这里看到在A部门下的A1部门还未同步,于是更新A1部门父节点信息,形成最终树结构:
至此,同步好通讯录部门树。
- 相关代码
主程序:
class DeptTree_Test
{
static void Main(string[] args)
{
TreeNode rootUC = new TreeNode()
{
NName = "组织",
NID = "1",
ParentNID = ""
};
TreeNode rootDept = new TreeNode()
{
NName = "组织",
NID = "1",
ParentNID = ""
};
DeptTree_Test p = new DeptTree_Test();
p.InitUC(); // 模拟从本地数据库读取公司部门信息
Console.WriteLine("===========公司部门===========");
p.PrintTree(rootUC, LocalUC); // 生成打印出公司部门树
p.InitDept(); // 模拟从通讯录下载部门信息
Console.WriteLine("===========通讯录===========");
p.PrintTree(rootDept, Dept); // 生成打印出通讯录部门树
rootDept.ChildrenNode.Clear();
p.MakeMap(); // 模拟读取『公司-通讯录』部门映射表
Console.WriteLine("===========映射表===========");
p.PrintTree(rootDept, Map); // 生成打印出映射表部门树
rootUC.ChildrenNode.Clear();
p.ChangeUC(); // 模拟公司部门信息更新
Console.WriteLine("===========公司部门改变===========");
p.PrintTree(rootUC, LocalUC); // 生成打印出新的公司部门树
Console.WriteLine("===========映射表增量变化===========");
var ltNewOU = p.treeManager.Subtract(rootUC, rootDept, TreeManager.MatchTag.NName); // 获取本地公司部门与映射表部门差异部门信息
List<DataRow> lToFixNewDP = new List<DataRow>();
// 先新增
foreach (var ou in ltNewOU)
{
//Console.WriteLine("{?} - {?},{?}({?})".Format_Ext(ou.NName, ou.NID, ou.ParentNID, JsonConvert.SerializeObject(ou.ExtraInfo)));
if (ou.ExtraInfo["Extra_DiffType"].ToString() == "MISSING") // 映射表中缺失的公司新增部门
{
DataRow nDr = Map.NewRow();
nDr[0] = ou.NName;
nDr[1] = p.autoIncreaseID++; // 模拟预新增通讯录部门ID
nDr[3] = ou.NID;
nDr[4] = ou.ParentNID;
Map.Rows.Add(nDr);
lToFixNewDP.Add(nDr);
}
}
// 然后确定父节点
foreach (DataRow mr in Map.Rows)
{
if (!lToFixNewDP.Exists(_d => _d[1].ToString() == mr[1].ToString())) continue;
foreach (DataRow r in Map.Rows)
{
if (r["Extra_UCID"].ToString() == mr[4].ToString())
{
mr[2] = r[1];
}
}
// 这里可以调用API添加到企业号(或先临时保存新增部门信息,后面统一更新)
}
rootDept.ChildrenNode.Clear();
p.PrintTree(rootDept, Map); // 打印出增量变化后的映射表部门树
Console.WriteLine("===========映射表父节点变化===========");
foreach (var ou in ltNewOU)
{
if (ou.ExtraInfo["Extra_DiffType"].ToString() == "DIFFERENT")
{
foreach (DataRow dr in Map.Rows)
{
if (dr["Extra_UCID"].ToString() == ou.NID)
{
foreach (DataRow pdr in Map.Rows)
{
if (pdr["Extra_UCID"].ToString() == ou.ParentNID)
{
dr[2] = pdr[1];
// 这里可以调用API更新到企业号(或先临时保存变化的部门信息,后面统一更新)
}
}
}
}
}
}
rootDept.ChildrenNode.Clear();
p.PrintTree(rootDept, Map); // 打印出父节点调节后的映射表部门树
//// 移除通讯录上多余的部门
//var ltToRmvDept = p.treeManager.Subtract(rootDept, rootUC, TreeManager.MatchTag.NName);
Console.WriteLine("Done");
Console.ReadKey();
}
TreeManager treeManager = new TreeManager();
public static DataTable Dept = new DataTable();
public static DataTable LocalUC = new DataTable();
public static DataTable Map = new DataTable();
int autoIncreaseID = 7;
void InitUC()
{
LocalUC.Columns.Add("NName");
LocalUC.Columns.Add("NID");
LocalUC.Columns.Add("ParentNID");
DataRow nDr = LocalUC.NewRow();
nDr[0] = "A"; nDr[1] = "1001000"; nDr[2] = "";
LocalUC.Rows.Add(nDr);
nDr = LocalUC.NewRow();
nDr[0] = "B"; nDr[1] = "1002000"; nDr[2] = "";
LocalUC.Rows.Add(nDr);
nDr = LocalUC.NewRow();
nDr[0] = "A1"; nDr[1] = "1001100"; nDr[2] = "1001000";
LocalUC.Rows.Add(nDr);
nDr = LocalUC.NewRow();
nDr[0] = "A2"; nDr[1] = "1001200"; nDr[2] = "1001000";
LocalUC.Rows.Add(nDr);
nDr = LocalUC.NewRow();
nDr[0] = "A11"; nDr[1] = "1001110"; nDr[2] = "1001100";
LocalUC.Rows.Add(nDr);
}
void ChangeUC()
{
LocalUC.Rows[2][2] = "1002000";
DataRow nDr = LocalUC.NewRow();
nDr[0] = "A3"; nDr[1] = "1001300"; nDr[2] = "1001000";
LocalUC.Rows.Add(nDr);
nDr = LocalUC.NewRow();
nDr[0] = "A21"; nDr[1] = "1001210"; nDr[2] = "1001200";
LocalUC.Rows.Add(nDr);
nDr = LocalUC.NewRow();
nDr[0] = "A211"; nDr[1] = "1001211"; nDr[2] = "1001210";
LocalUC.Rows.Add(nDr);
nDr = LocalUC.NewRow();
nDr[0] = "B1"; nDr[1] = "1002100"; nDr[2] = "1002000";
LocalUC.Rows.Add(nDr);
}
void InitDept()
{
Dept.Columns.Add("NName");
Dept.Columns.Add("NID");
Dept.Columns.Add("ParentNID");
DataRow nDr = Dept.NewRow();
nDr[0] = "A"; nDr[1] = "2"; nDr[2] = "";
Dept.Rows.Add(nDr);
nDr = Dept.NewRow();
nDr[0] = "B"; nDr[1] = "3"; nDr[2] = "";
Dept.Rows.Add(nDr);
nDr = Dept.NewRow();
nDr[0] = "A1"; nDr[1] = "4"; nDr[2] = "2";
Dept.Rows.Add(nDr);
nDr = Dept.NewRow();
nDr[0] = "A2"; nDr[1] = "5"; nDr[2] = "2";
Dept.Rows.Add(nDr);
nDr = Dept.NewRow();
nDr[0] = "A11"; nDr[1] = "6"; nDr[2] = "4";
Dept.Rows.Add(nDr);
}
void MakeMap()
{
Map = new DataTable();
Map.Columns.Add("NName");
Map.Columns.Add("NID");
Map.Columns.Add("ParentNID");
Map.Columns.Add("Extra_UCID");
Map.Columns.Add("Extra_ParentUCID");
foreach (DataRow dp in Dept.Rows)
{
DataRow nDr = Map.NewRow();
nDr[0] = dp[0];
nDr[1] = dp[1];
nDr[2] = dp[2];
foreach (DataRow uc in LocalUC.Rows)
{
if (uc[0] == dp[0])
{
nDr[3] = uc[1];
nDr[4] = uc[2];
break;
}
}
Map.Rows.Add(nDr);
}
}
void PrintTree(TreeNode root, DataTable dt)
{
treeManager.BuildTree("", root, dt, 0);
var lst = treeManager.Traverse(root);
StringBuilder sb = new StringBuilder();
sb.AppendLine(lst[0].NName);
for (int i = 1; i < lst.Count; i++)
{
var item = lst[i];
string str = "";
for (int j = 0; j <= item.DeepIndex; j++)
{
str += "\t";
}
if (item.ExtraInfo.Count == 0)
{
sb.AppendLine(str + item.NName + "(" + item.NID + ")");
}
else
{
sb.AppendLine(str + item.NName + "(" + item.NID + ":" + item.ExtraInfo.ElementAt(0).Value.ToString() + ")");
}
}
Console.WriteLine(sb.ToString());
}
}
树算法类:
public class TreeManager
{
public enum MatchTag
{
NID,
NName
}
public class ExtraInfo
{
public string Key = "";
public object Value = "";
}
public int TreeDeep = 0;
public void BuildTree(string parentNID, TreeNode tn, DataTable dt, int dp)
{
if (dp > 100) throw new Exception("Too Deep!");
if (dp > TreeDeep) TreeDeep = dp;
foreach (DataRow dr in dt.Rows)
{
if ((parentNID == null && dr["ParentNID"] == null)
|| (parentNID != null && dr["ParentNID"].ToString() == parentNID))
{
var o = new TreeNode()
{
NID = dr["NID"].ToString().Trim(),
NName = dr["NName"].ToString().Trim(),
ParentNID = parentNID,
DeepIndex = dp
};
foreach (DataColumn dc in dt.Columns)
{
if (dc.ColumnName.StartsWith("Extra_"))
{
o.ExtraInfo.Add(dc.ColumnName, dr[dc.ColumnName]);
}
}
o.ParentNode = tn;
tn.ChildrenNode.Add(o);
BuildTree(o.NID, o, dt, dp + 1);
}
}
}
public TreeNode FindInTree(TreeNode tnode, string nid, string nname, ExtraInfo extraInfo)
{
if (tnode == null) return null;
TreeNode tn = null;
if (tn == null && !string.IsNullOrEmpty(nid))
{
if (tnode.NID == nid) tn = tnode;
}
if (tn == null && !string.IsNullOrEmpty(nname))
{
if (tnode.NName == nname) tn = tnode;
}
if (tn == null && extraInfo != null)
{
foreach (string k in tnode.ExtraInfo.Keys)
{
if (k == extraInfo.Key
&& extraInfo.Value.ToString() == tnode.ExtraInfo[k].ToString())
{
tn = tnode;
return tn;
}
}
}
if (tn == null && tnode.ChildrenNode.Count > 0)
{
foreach (var item in tnode.ChildrenNode)
{
tn = FindInTree(item, nid, nname, extraInfo);
if (tn != null) break;
}
}
return tn;
}
/// <summary>
/// 匹配树结构
/// </summary>
/// <param name="root1"></param>
/// <param name="root2"></param>
/// <param name="mt">根据节点ID/节点名称进行匹对</param>
/// <returns></returns>
public List<TreeNode> Subtract(TreeNode root1, TreeNode root2, MatchTag mt = MatchTag.NID)
{
List<TreeNode> lTN = new List<TreeNode>();
var lPath1 = Traverse(root1);
var lPath2 = Traverse(root2);
bool pass = false;
foreach (var item in lPath1)
{
pass = false;
for (int i = 0; i < lPath2.Count; i++)
{
if (mt == MatchTag.NID)
{
if (lPath2[i].NID == item.NID)
{
if (((lPath2[i].ParentNode != null && item.ParentNode != null && lPath2[i].ParentNode.NID == item.ParentNode.NID)
|| lPath2[i].ParentNode == null && item.ParentNode == null))
{
lPath2.RemoveAt(i--);
pass = true;
break;
}
item.ExtraInfo.Add("Extra_DiffType", "DIFFERENT");
}
}
else if (mt == MatchTag.NName)
{
if (lPath2[i].NName == item.NName)
{
if (((lPath2[i].ParentNode != null && item.ParentNode != null && lPath2[i].ParentNode.NName == item.ParentNode.NName)
|| lPath2[i].ParentNode == null && item.ParentNode == null))
{
lPath2.RemoveAt(i--);
pass = true;
break;
}
item.ExtraInfo.Add("Extra_DiffType", "DIFFERENT");
}
}
}
if (!pass)
{
if (!item.ExtraInfo.ContainsKey("Extra_DiffType"))
item.ExtraInfo.Add("Extra_DiffType", "MISSING");
lTN.Add(item);
}
}
return lTN;
}
/// <summary>
/// 遍历树(纵向)
/// </summary>
/// <param name="root"></param>
/// <returns></returns>
public List<TreeNode> Traverse(TreeNode root)
{
List<TreeNode> lPath = new List<TreeNode>();
lPath.Add(root);
if (root.ChildrenNode.Count > 0)
{
foreach (var item in root.ChildrenNode)
{
foreach (var n in Traverse(item))
{
lPath.Add(n);
}
}
}
return lPath;
}
/// <summary>
/// 遍历树(横向)
/// </summary>
/// <param name="root"></param>
/// <returns></returns>
public List<TreeNode> TraverseByLayer(TreeNode root)
{
List<TreeNode> lPath = new List<TreeNode>();
lPath.Add(root);
TreeNode tn = root;
Queue<TreeNode> qToTraverseTN = new Queue<TreeNode>();
List<TreeNode> lToTraverseTN = new List<TreeNode>();
do
{
foreach (var item in tn.ChildrenNode)
{
lPath.Add(item);
qToTraverseTN.Enqueue(item);
}
if (qToTraverseTN.Count == 0) break;
tn = qToTraverseTN.Dequeue();
} while (true);
return lPath;
}
}
public class TreeNode
{
public int DeepIndex = 0;
public string NID;
public string NName;
public string ParentNID;
public Dictionary<string, object> ExtraInfo = new Dictionary<string, object>();
public TreeNode ParentNode = null;
public List<TreeNode> ChildrenNode = new List<TreeNode>();
public int TotalChildrenNodeCount()
{
int childrenOUTotalCount = ChildrenNode.Count;
foreach (var item in ChildrenNode)
{
int tc = item.TotalChildrenNodeCount();
childrenOUTotalCount += tc;
}
return childrenOUTotalCount;
}
}