This commit is contained in:
owenchen committed 2026-10-23 09:46:48 +08:00
1 parent 94d5fb2ec9
commit 6c86c59bdc
15 files changed
+429 -14

No files matched your search

@@ -1396,5 +1396,64 @@ namespace CloudBuilder.Master.Service
}
catch (Exception ex) { Console.WriteLine($"[FamilyTreeExchange] 审计写入失败: {ex.Message}"); }
}
/// <summary>
/// 族谱「统计」 tab 的聚合查询:总数/男/女/世代层数 + 各世代人数分布。
/// 世代 = 始祖起 BFS 深度(始祖=1 世);未连到始祖的旁支不入世代桶,只计入 Total。
/// 与 ListExchangeFiles/SaveExchangeFile 同套成员闸:非本族成员不能拉本族聚合数据。
/// </summary>
[PermissionOperate(name: MasterPermissionPolicy.FamilyTree, operate: Operate.Find)]
public FamilyTreeStatisticsData GetStatistics(string treeId)
{
FamilyTreeStatisticsData data = new FamilyTreeStatisticsData();
if (string.IsNullOrEmpty(treeId)) return data;
FamilyTreeEntity d = repository.DetachedEntities.Where(x => x.TreeId == treeId).FirstOrDefault();
if (d == null) throw new ValidatedException("族谱不存在.");
var helper = new FamilyTreePermissionHelper(service);
if (!helper.IsMember(treeId) && !helper.IsManager(treeId))
throw new ValidatedException("您不是该族谱成员.");
IRepository<FamilyPersonEntity> repositoryPerson = repository.GetRepository<IRepository<FamilyPersonEntity>>();
IRepository<FamilyTreeChildrenMapEntity> repositoryChildrenMap = repository.GetRepository<IRepository<FamilyTreeChildrenMapEntity>>();
FamilyPersonEntity[] persons = repositoryPerson.DetachedEntities.Where(x => x.TreeId == treeId).ToArray();
FamilyTreeChildrenMapEntity[] childrenMaps = repositoryChildrenMap.DetachedEntities.Where(x => x.TreeId == treeId).ToArray();
data.Total = persons.Length;
data.Male = persons.Count(x => x.Gender == "M");
data.Female = persons.Count(x => x.Gender == "W");
// BFS 从始祖起逐层下钻,记录每人世代号;genByPerson 去重兼做环保护
Dictionary<string, int> genByPerson = new Dictionary<string, int>();
if (!string.IsNullOrEmpty(d.AncestorPersonId))
{
Queue<KeyValuePair<string, int>> q = new Queue<KeyValuePair<string, int>>();
q.Enqueue(new KeyValuePair<string, int>(d.AncestorPersonId, 1));
genByPerson[d.AncestorPersonId] = 1;
while (q.Count > 0)
{
var cur = q.Dequeue();
foreach (var c in childrenMaps.Where(x => x.PersonId == cur.Key))
{
if (string.IsNullOrEmpty(c.ChildrenId) || genByPerson.ContainsKey(c.ChildrenId)) continue;
genByPerson[c.ChildrenId] = cur.Value + 1;
q.Enqueue(new KeyValuePair<string, int>(c.ChildrenId, cur.Value + 1));
}
}
}
data.GenerationCount = genByPerson.Count > 0 ? genByPerson.Values.Max() : 0;
// 按世代升序聚合;未入桶的旁支不入世代分布(仅计 Total)
data.PerGeneration = genByPerson.Values
.GroupBy(g => g)
.OrderBy(grp => grp.Key)
.Select(grp => new FamilyTreeGenerationCountData { Generation = grp.Key, Count = grp.Count() })
.ToArray();
return data;
}
}
}
@@ -0,0 +1,37 @@
using CloudBuilder.Core.Authorization;
namespace CloudBuilder.Master.Data
{
/// <summary>
/// 族谱「统计」tab 的聚合数据:总数/男/女/世代层数 + 各世代人数分布。
/// 后端单次遍历算完(族员表 + 亲子边表 + 始祖 BFS),避免前端拉全表再算;
/// 世代 = 始祖起 BFS 深度(始祖=1 世),未连到始祖的旁支不入世代桶,只计入 Total。
/// </summary>
[TypeScript]
public class FamilyTreeStatisticsData
{
/// <summary>入树成员总数(family_person 按 TreeId 过滤后的行数)</summary>
public int Total { get; set; }
/// <summary>男性族员数(Gender=M)</summary>
public int Male { get; set; }
/// <summary>女性族员数(Gender=W,GenderPolicy 里女= W 而非 F)</summary>
public int Female { get; set; }
/// <summary>世代层数(本谱出现过的最大世代号;空谱为 0)</summary>
public int GenerationCount { get; set; }
/// <summary>各世代人数分布(按世代升序;空档世代也占位,图表柱不连续)</summary>
public FamilyTreeGenerationCountData[] PerGeneration { get; set; } = [];
}
/// <summary>单个世代的人数条目(世代号 + 该世代族员数)</summary>
[TypeScript]
public class FamilyTreeGenerationCountData
{
public int Generation { get; set; }
public int Count { get; set; }
}
}
@@ -34,5 +34,8 @@ namespace CloudBuilder.Master.Service
FamilyTreeExchangeFileInfo[] ListBackups(string treeId);
int RestoreBackup(string targetTreeId, string backupFileName);
/// <summary>族谱「统计」 tab 的聚合数据(总数/男女/世代层数/各世代人数);仅本族成员可拉</summary>
FamilyTreeStatisticsData GetStatistics(string treeId);
}
}
@@ -176,6 +176,10 @@ return Task.Run(() => serviceRequest.PostAsJsonAsync<FamilyTreeExchangeFileInf
{
var compositionData = new RestoreBackupCompositionData2{ ␍targetTreeId = targetTreeId, ␍backupFileName = backupFileName }; ␍return Task.Run(() => serviceRequest.PostAsJsonAsync<RestoreBackupCompositionData2, Int32>("/FamilyTree/RestoreBackup", compositionData)).GetAwaiter().GetResult(); }
public FamilyTreeStatisticsData GetStatistics(string treeId)
{
return Task.Run(() => serviceRequest.PostAsJsonAsync<FamilyTreeStatisticsData>("/FamilyTree/GetStatistics?treeId=" + treeId.ToString())).GetAwaiter().GetResult(); }
}
}
@@ -551,6 +551,26 @@ this.familyTreeService= familyTreeService;
}
}
[PermissionOperate(name: "FamilyTree", operate: "Find")]
[HttpPost]
public dynamic GetStatistics(string treeId)
{
try
{
var result = familyTreeService.GetStatistics(treeId);
return result;
}
catch (ValidatedException vex)
{
return vex;
}
catch (Exception ex)
{
return new ServiceErrorException(ex);
}
}
}
}
@@ -3,7 +3,7 @@
<Project>
<PropertyGroup>
<_PublishTargetUrl>C:\inetpub\wwwroot\dockwebapi2</_PublishTargetUrl>
<History>True|2026-10-22T06:27:02.4892588Z||;True|2026-10-21T21:22:01.1456473+08:00||;True|2026-10-20T15:45:39.3393674+08:00||;True|2026-10-17T20:49:21.0401213+08:00||;True|2026-09-23T13:19:13.0148919+08:00||;True|2026-09-23T13:16:07.8584304+08:00||;True|2026-09-22T23:03:13.6509491+08:00||;True|2026-09-22T16:07:15.9177123+08:00||;True|2026-09-22T11:19:19.6087109+08:00||;True|2026-09-22T11:09:24.9165065+08:00||;True|2026-09-21T22:40:07.1232044+08:00||;True|2026-09-21T22:35:06.9446571+08:00||;True|2026-09-21T22:27:42.1146825+08:00||;False|2026-09-21T22:27:17.6066573+08:00||;True|2026-09-21T21:01:22.8888913+08:00||;True|2026-09-21T19:15:03.4753747+08:00||;True|2026-09-21T18:08:30.0866986+08:00||;True|2026-09-21T16:51:19.8137872+08:00||;True|2026-09-21T16:30:01.0950896+08:00||;True|2026-09-21T15:09:00.2326737+08:00||;True|2026-09-21T14:47:09.6120256+08:00||;True|2026-09-21T14:16:35.3194409+08:00||;True|2026-09-21T13:54:58.2829712+08:00||;True|2026-09-21T13:36:45.3134499+08:00||;True|2026-09-19T09:48:52.0085130+08:00||;False|2026-09-19T09:47:56.1658214+08:00||;True|2026-09-17T18:50:15.9493380+08:00||;True|2026-09-17T18:11:11.5364547+08:00||;True|2026-09-17T18:07:27.7830758+08:00||;True|2026-09-17T17:58:16.5798560+08:00||;True|2026-09-17T15:37:30.9025614+08:00||;True|2026-09-17T15:25:46.9116292+08:00||;True|2026-09-17T15:05:18.6592403+08:00||;True|2026-09-17T14:57:28.7997437+08:00||;True|2026-09-17T14:36:00.6339386+08:00||;True|2026-09-17T13:56:43.2376873+08:00||;True|2026-09-16T13:56:51.8724546+08:00||;True|2026-09-15T20:44:46.6559899+08:00||;True|2026-09-13T19:02:59.5654245+08:00||;True|2026-09-10T09:14:56.2914193+08:00||;True|2026-09-09T16:40:16.6461154+08:00||;True|2026-09-08T13:55:23.1502435+08:00||;True|2026-09-08T08:04:53.4279263+08:00||;True|2026-09-07T08:48:17.4682304+08:00||;True|2026-09-04T14:39:08.7878670+08:00||;True|2026-09-04T14:35:48.2937877+08:00||;True|2026-09-04T11:45:16.1372876+08:00||;True|2026-09-04T11:42:59.9356436+08:00||;True|2026-09-03T16:05:52.5270232+08:00||;True|2026-09-03T14:52:56.8559144+08:00||;True|2026-09-02T11:08:23.8720038+08:00||;True|2026-09-02T10:58:16.8081797+08:00||;True|2026-08-26T13:34:12.3805892+08:00||;True|2026-08-25T08:31:30.7787234+08:00||;True|2026-08-20T13:10:46.4725468+08:00||;True|2026-08-20T13:10:28.3142576+08:00||;True|2026-08-19T16:10:27.3362887+08:00||;True|2026-08-19T16:01:12.6234971+08:00||;True|2026-08-19T15:42:46.6576265+08:00||;True|2026-08-19T13:39:59.9363522+08:00||;True|2026-08-19T13:36:53.0379313+08:00||;True|2026-08-19T13:31:36.0841398+08:00||;True|2026-08-19T11:44:37.5319626+08:00||;True|2026-08-19T09:36:45.6000047+08:00||;True|2026-08-19T09:34:36.8468631+08:00||;True|2026-08-19T08:35:41.2202300+08:00||;True|2026-08-18T16:20:59.0170916+08:00||;</History>
<History>True|2026-10-23T01:45:02.7814696Z||;True|2026-10-22T14:27:02.4892588+08:00||;True|2026-10-21T21:22:01.1456473+08:00||;True|2026-10-20T15:45:39.3393674+08:00||;True|2026-10-17T20:49:21.0401213+08:00||;True|2026-09-23T13:19:13.0148919+08:00||;True|2026-09-23T13:16:07.8584304+08:00||;True|2026-09-22T23:03:13.6509491+08:00||;True|2026-09-22T16:07:15.9177123+08:00||;True|2026-09-22T11:19:19.6087109+08:00||;True|2026-09-22T11:09:24.9165065+08:00||;True|2026-09-21T22:40:07.1232044+08:00||;True|2026-09-21T22:35:06.9446571+08:00||;True|2026-09-21T22:27:42.1146825+08:00||;False|2026-09-21T22:27:17.6066573+08:00||;True|2026-09-21T21:01:22.8888913+08:00||;True|2026-09-21T19:15:03.4753747+08:00||;True|2026-09-21T18:08:30.0866986+08:00||;True|2026-09-21T16:51:19.8137872+08:00||;True|2026-09-21T16:30:01.0950896+08:00||;True|2026-09-21T15:09:00.2326737+08:00||;True|2026-09-21T14:47:09.6120256+08:00||;True|2026-09-21T14:16:35.3194409+08:00||;True|2026-09-21T13:54:58.2829712+08:00||;True|2026-09-21T13:36:45.3134499+08:00||;True|2026-09-19T09:48:52.0085130+08:00||;False|2026-09-19T09:47:56.1658214+08:00||;True|2026-09-17T18:50:15.9493380+08:00||;True|2026-09-17T18:11:11.5364547+08:00||;True|2026-09-17T18:07:27.7830758+08:00||;True|2026-09-17T17:58:16.5798560+08:00||;True|2026-09-17T15:37:30.9025614+08:00||;True|2026-09-17T15:25:46.9116292+08:00||;True|2026-09-17T15:05:18.6592403+08:00||;True|2026-09-17T14:57:28.7997437+08:00||;True|2026-09-17T14:36:00.6339386+08:00||;True|2026-09-17T13:56:43.2376873+08:00||;True|2026-09-16T13:56:51.8724546+08:00||;True|2026-09-15T20:44:46.6559899+08:00||;True|2026-09-13T19:02:59.5654245+08:00||;True|2026-09-10T09:14:56.2914193+08:00||;True|2026-09-09T16:40:16.6461154+08:00||;True|2026-09-08T13:55:23.1502435+08:00||;True|2026-09-08T08:04:53.4279263+08:00||;True|2026-09-07T08:48:17.4682304+08:00||;True|2026-09-04T14:39:08.7878670+08:00||;True|2026-09-04T14:35:48.2937877+08:00||;True|2026-09-04T11:45:16.1372876+08:00||;True|2026-09-04T11:42:59.9356436+08:00||;True|2026-09-03T16:05:52.5270232+08:00||;True|2026-09-03T14:52:56.8559144+08:00||;True|2026-09-02T11:08:23.8720038+08:00||;True|2026-09-02T10:58:16.8081797+08:00||;True|2026-08-26T13:34:12.3805892+08:00||;True|2026-08-25T08:31:30.7787234+08:00||;True|2026-08-20T13:10:46.4725468+08:00||;True|2026-08-20T13:10:28.3142576+08:00||;True|2026-08-19T16:10:27.3362887+08:00||;True|2026-08-19T16:01:12.6234971+08:00||;True|2026-08-19T15:42:46.6576265+08:00||;True|2026-08-19T13:39:59.9363522+08:00||;True|2026-08-19T13:36:53.0379313+08:00||;True|2026-08-19T13:31:36.0841398+08:00||;True|2026-08-19T11:44:37.5319626+08:00||;True|2026-08-19T09:36:45.6000047+08:00||;True|2026-08-19T09:34:36.8468631+08:00||;True|2026-08-19T08:35:41.2202300+08:00||;True|2026-08-18T16:20:59.0170916+08:00||;</History>
<LastFailureDetails />
</PropertyGroup>
</Project>