AdoS7FlowAuthorityMigrationLog.cs 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. namespace Admin.NET.Plugin.AiDOP.Entity.S7;
  2. /// <summary>
  3. /// S7-LEGACY-SNAPSHOT-MIGRATION-1:S7 成品检验存量运行中实例的审批权威快照迁移留证。
  4. ///
  5. /// <para><b>为什么必须留证</b>:<c>ApprovalFlowInstance</c> 没有 version、没有 checksum、
  6. /// 没有并发令牌,<c>UpdateTime</c> 也不是 DB 自动列 —— 裸 UPDATE 之后
  7. /// <b>无法证明改过什么、为什么改、改前是什么</b>。而 <c>FlowJsonSnapshot</c> 同时是
  8. /// 通用审批中心渲染历史流程图的数据源,没有这张表这就是一次无法解释的静默改写。</para>
  9. ///
  10. /// <para><b>为什么不复用 <c>ado_s6_flow_authority_migration_log</c></b>:那张表的 schema 确实是
  11. /// 通用的(batch / instance / biz / tenant / before-after / md5 / mapping / rollback / outcome),
  12. /// 但它的**表名与文档语义都限定在 S6**。把 S7 的迁移事实写进「S6 迁移留证」表,会让
  13. /// 未来任何人按表名审计 S6 时读到 S7 行、按 S7 审计时找不到表 —— 可审计性反而变差。
  14. /// 故本表独立建立,字段有意与 S6 对齐以便将来合并,并按本批要求额外落
  15. /// <see cref="ChangedPaths"/> / <see cref="AppliedBy"/> / <see cref="MigrationKey"/>。</para>
  16. ///
  17. /// <para><b>append-only。</b>与 <c>AdoS8ApprovalFlowRepairLog</c>(每轮 DELETE 后整体重写,
  18. /// 描述「最近一次对账看到了什么」)语义不同:迁移是**一次性历史事件**,记录一旦写下
  19. /// 不允许被后续运行抹掉,否则回滚依据会随时间消失。</para>
  20. ///
  21. /// <para><b>幂等不依赖本表。</b>「是否已迁」的判据是快照自身内容(Role 节点的 approverIds
  22. /// 是否还存在纯数字 token),不是「本表里有没有这条 InstanceId」——
  23. /// 留证一旦丢失就重复改写,是 S8 先例踩过的坑。</para>
  24. /// </summary>
  25. [SugarTable("ado_s7_flow_authority_migration_log", "S7 存量流程审批权威迁移留证(append-only)")]
  26. public class AdoS7FlowAuthorityMigrationLog
  27. {
  28. [SugarColumn(ColumnName = "id", IsPrimaryKey = true, IsIdentity = true, ColumnDataType = "bigint")]
  29. public long Id { get; set; }
  30. /// <summary>迁移批次标识,同一次执行的所有记录共用,便于整批回滚定位。</summary>
  31. [SugarColumn(ColumnName = "migration_batch", Length = 64)]
  32. public string MigrationBatch { get; set; } = string.Empty;
  33. /// <summary>
  34. /// 稳定的单实例迁移键(<c>S7-LEGACY-SNAPSHOT-MIGRATION-1#&lt;instanceId&gt;</c>)。
  35. /// 与 <see cref="MigrationBatch"/> 的区别:batch 含时间戳、每次执行都不同;
  36. /// 本字段跨执行稳定,用于「同一实例是否已被成功迁过」的重复留证检测。
  37. /// </summary>
  38. [SugarColumn(ColumnName = "migration_key", Length = 96)]
  39. public string MigrationKey { get; set; } = string.Empty;
  40. [SugarColumn(ColumnName = "instance_id", ColumnDataType = "bigint")]
  41. public long InstanceId { get; set; }
  42. [SugarColumn(ColumnName = "biz_type", Length = 64)]
  43. public string BizType { get; set; } = string.Empty;
  44. [SugarColumn(ColumnName = "biz_id", ColumnDataType = "bigint")]
  45. public long BizId { get; set; }
  46. /// <summary>由 S7 业务实体(<c>qms_qcpp_inspbill.tenant_id</c>)反查得到,**不取登录用户租户**。</summary>
  47. [SugarColumn(ColumnName = "business_tenant_id", ColumnDataType = "bigint")]
  48. public long BusinessTenantId { get; set; }
  49. /// <summary>改前快照全文,逐字保留 —— 第一回滚来源。</summary>
  50. [SugarColumn(ColumnName = "before_snapshot", ColumnDataType = StaticConfig.CodeFirst_BigString, IsNullable = true)]
  51. public string? BeforeSnapshot { get; set; }
  52. /// <summary>改后快照全文。回滚前用它比对「现值确实是我改成的那样」。</summary>
  53. [SugarColumn(ColumnName = "after_snapshot", ColumnDataType = StaticConfig.CodeFirst_BigString, IsNullable = true)]
  54. public string? AfterSnapshot { get; set; }
  55. [SugarColumn(ColumnName = "before_md5", Length = 32, IsNullable = true)]
  56. public string? BeforeMd5 { get; set; }
  57. [SugarColumn(ColumnName = "after_md5", Length = 32, IsNullable = true)]
  58. public string? AfterMd5 { get; set; }
  59. /// <summary>
  60. /// 本次实际改写的 JSON 路径清单,逐条形如
  61. /// <c>nodes[id=N2_SUP_REVIEW].properties.approverIds</c>。
  62. /// 用于回答「到底动了哪几个字段」,并支撑「只允许 N2/N3 两个节点」的门禁复核。
  63. /// </summary>
  64. [SugarColumn(ColumnName = "changed_paths", ColumnDataType = StaticConfig.CodeFirst_BigString, IsNullable = true)]
  65. public string? ChangedPaths { get; set; }
  66. /// <summary>逐节点映射:<c>node=N2_SUP_REVIEW;1329915020002=&gt;ROLE_S7_FQC_SUPERVISOR@848821564117061;members=1</c>。</summary>
  67. [SugarColumn(ColumnName = "authority_mapping", ColumnDataType = StaticConfig.CodeFirst_BigString, IsNullable = true)]
  68. public string? AuthorityMapping { get; set; }
  69. /// <summary>第二回滚来源:可反查出原始 FlowJson 的 ApprovalFlowVersion 行 Id。</summary>
  70. [SugarColumn(ColumnName = "rollback_source", Length = 128, IsNullable = true)]
  71. public string? RollbackSource { get; set; }
  72. /// <summary>为什么改 / 为什么没改:判据与依据。</summary>
  73. [SugarColumn(ColumnName = "reason", Length = 1024, IsNullable = true)]
  74. public string? Reason { get; set; }
  75. /// <summary>
  76. /// 迁移执行身份。本迁移在应用启动时以系统身份运行、无登录用户上下文,
  77. /// 故记录「执行体 + 主机」而不是某个 UserId —— 写一个假的 UserId 比留空更糟。
  78. /// </summary>
  79. [SugarColumn(ColumnName = "applied_by", Length = 128, IsNullable = true)]
  80. public string? AppliedBy { get; set; }
  81. /// <summary>APPLIED / BLOCKED_TARGET_AUTHORITY / BLOCKED_ROLLBACK_SOURCE / BLOCKED_CHANGED_PATH / BLOCKED_CONCURRENT_MODIFICATION。</summary>
  82. [SugarColumn(ColumnName = "outcome", Length = 48)]
  83. public string Outcome { get; set; } = string.Empty;
  84. [SugarColumn(ColumnName = "applied_at")]
  85. public DateTime AppliedAt { get; set; } = DateTime.Now;
  86. }