【树结构数据同步】公司部门—通讯录数据同步
- 需求
将本地公司部门信息更新到微信企业号通讯录(可改进为定期更新)
- 说明
保存在本地数据库的公司部门信息,采用类似如下格式保存:
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; } }