Files
flow-center-service/src/main/java/com/yunhai/flowcenter/entity/FlwInstance.java
T
chenxing d72128672f
build / build-push (push) Failing after 55s
feat(fl2): 落地部门目录 / 退回重提(D14) / 授权管理后端(基线 origin/main)
在 b369527 之上增量移植 FL2.0 后端能力,不改既有线级停发/指派预览/流程图入口:

- 部门目录:FlowDepartmentController(/admin/department/{groups,options,resolve,members})
  + UserBaseDepartmentDirectory(先按会话校域解析引用,再读成员;目录不可用 fail-closed)
- 退回重提 D14:POST /oapi/flow/instance/{instanceId}/resubmit,仅原提交人 CAS 路径放行,
  OAPI 侧 submitterId 路径继续 fail-closed
- 授权管理:/api/flow/admin/usage-grant/{schoolList,summary}(校域取管理员会话,无 schId 入参)
- 来源主体:解析器派生 ub-app:{applicationInfo.id},不再读取 UserBase 的 sourcePrincipalId 字段
- 回调来源策略:SELF 身份强校验;NULL 来源不广播;复合幂等键 (event_id,reg_id)
- 测试夹具:许可/主体/开通桩按新契约修正,FlowEngineIT/FormalApplyCommandIT/
  CallbackDispatchIT/OapiFlowContractIT 共 117 例在真实 MySQL 8 通过

不含:FlSourceAuthorizationTest(其断言的两个 Repository 派生方法属另一实现线)
2026-10-30 10:15:12 +08:00

184 lines
8.1 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package com.yunhai.flowcenter.entity;
import com.yunhai.flowcenter.enums.FlwEnums;
import lombok.Getter;
import lombok.Setter;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Index;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import javax.persistence.Version;
import java.time.LocalDateTime;
/**
* 办件实例(概览 §4.3 + V2.0 Part D):托管模式下业务对象留在业务方,本表只存
* biz_title/biz_url/biz_snapshot 快照与关联键。正式命令每轮独立实例,唯一域为
* (school, sourcePrincipalId, bizType, bizId, applyRound),单在途由坐标表事务指针保护。
* sourcePrincipalId=NULL / legacySlot=1 仅保留迁移前旧入口的一业务一行语义;正式模式关闭旧写入口。
*
* <p>version 是乐观锁(推进串行化)兼回调 payload 的 instanceVersion(业务方按它丢弃乱序旧事件),
* 由 JPA @Version 在 flush 时自增。
*/
@Getter
@Setter
@Entity
@Table(name = "flw_instance",
uniqueConstraints = {
@UniqueConstraint(name = "uk_flw_instance_legacy", columnNames = {"sch_id", "source_principal_id", "biz_type", "biz_id", "legacy_slot"}),
@UniqueConstraint(name = "uk_flw_instance_round", columnNames = {"sch_id", "source_principal_id", "biz_type", "biz_id", "apply_round"})},
indexes = {
@Index(name = "idx_flw_inst_submitter", columnList = "sch_id, submitter_id, status"),
@Index(name = "idx_flw_inst_status_time", columnList = "sch_id, status, submit_time")})
public class FlwInstance extends BaseEntity {
@Column(name="source_principal_id",length=36,updatable=false)
private String sourcePrincipalId;
/** 1 for legacy writers, NULL for formal commands. Migration must drop the old three-column UK. */
@Column(name="legacy_slot")
private Integer legacySlot = 1;
/** 租户键 */
@Column(name = "sch_id", nullable = false)
private Long schId;
/** 定义快照(发布版本 id;重提重启时刷新为当前已发布版本) */
@Column(name = "def_id", nullable = false)
private Long defId;
/** 定义编码快照 */
@Column(name = "def_code", nullable = false, length = 64)
private String defCode;
/** 定义版本快照 */
@Column(name = "def_version", nullable = false)
private Integer defVersion;
/** 表单提交快照(无表单托管模式为 NULL,透传存业务方 formData) */
@Column(name = "form_data_json", columnDefinition = "LONGTEXT")
private String formDataJson;
/** 发起时指派快照:{nodeId:[{userId,userName}]};NULL=无指派(全量候选现状行为) */
@Column(name = "assign_json", columnDefinition = "LONGTEXT")
private String assignJson;
/** CMS 事项快照(原生模式,M1 不启用恒 null) */
@Column(name = "item_code", length = 64)
private String itemCode;
@Column(name = "item_title", length = 200)
private String itemTitle;
/** 业务类型键(外部托管模式,如 svc_item) */
@Column(name = "biz_type", length = 32)
private String bizType;
/** 业务主键(如 svc-hall applyId) */
@Column(name = "biz_id", length = 64)
private String bizId;
/** 办件标题快照:待办列表/消息/回调 payload 统一携带 */
@Column(name = "biz_title", length = 200)
private String bizTitle;
/** 业务方详情页链接:办理页"查看业务详情"跳转入口 */
@Column(name = "biz_url", length = 500)
private String bizUrl;
/**
* 代发起调用方 app_key 快照(验签上下文 yh.app.auth,2026-10-12):验签域身份,
* 审计/对账用——与 flw_callback_reg/flw_def 的 app_key 同一口径。
*/
@Column(name = "apply_app_key", length = 32)
private String applyAppKey;
/**
* 代发起调用方 app_code 快照(apply 时按 app_key 查 UserBase /oapi/app/by-key 换得):
* 消息 jump_app 的权威来源(路由/跳转/判权域标识,projman app.name 口径如 notice/svc-hall)。
* biz_url 页面即调用方页面,验签身份=页面属主由平台结构保证。UserBase 不可达时为空
* →消息 jumpApp 空、回落发送方归属(现状行为),不阻断 apply。
*/
@Column(name = "apply_app_code", length = 32)
private String applyAppCode;
/** 业务摘要 JSON 快照:办理页展示用(只收摘要,不收富文本正文,概览 §十边界) */
@Column(name = "biz_snapshot", columnDefinition = "LONGTEXT")
private String bizSnapshot;
/** 提交人 id(外部模式=业务方当前登录人快照,本校校验在入口做) */
@Column(name = "submitter_id", nullable = false)
private Integer submitterId;
/** 提交人姓名快照 */
@Column(name = "submitter_name", nullable = false, length = 128)
private String submitterName;
/** 10 审批中 / 20 已完成 / 30 已拒绝 / 40 已撤回 */
@Column(name = "status", nullable = false)
private Integer status = FlwEnums.InstanceStatus.APPROVING.getCode();
/** 当前活跃节点 id 集合(冗余逗号分隔,列表页免展开) */
@Column(name = "current_nodes", length = 200)
private String currentNodes;
@Column(name = "submit_time")
private LocalDateTime submitTime;
@Column(name = "finish_time")
private LocalDateTime finishTime;
/**
* 乐观锁版本:推进串行化 + 回调 instanceVersion 单调递增。
* 注意不可初始化(=0 会让 Spring Data 判定非新实体走 merge,id 不回填);
* insert 时由 Hibernate 写入 0。
*/
@Version
@Column(name = "version", nullable = false)
private Integer version;
/**
* 最近一次成功受理代发起的<b>请求标识</b>(消费方持久化命令里带的 token,v2 §8,TASK_2 C.3)。
*
* <p>它回答的是"这次 apply 是同一命令的重投,还是新一轮发起"——biz uk 只说得出
* "这个业务键已经有办件了",说不出这句。没有它时,消费方在响应丢失后重发同业务键,
* 命中 30/40 会被 R4 语义重启推进,等于<b>把网络重试当成用户重提</b>。
* 同 token 一律只回读现状、不重启;换 token 才是新一轮。</p>
*/
@Column(name = "apply_request_token", length = 64)
private String applyRequestToken;
/**
* 当前发起轮次(1 起)。正式命令由服务端坐标锁内分配,不接受客户端 attempt 分轮。
*
* <p>轮次守卫是<b>单调</b>的:请求带来的 attempt 低于行上的 applyRound 就是陈旧轮次的迟到重投,
* 只回读现状。两侧(消费方命令行 + 本行)都持久化,崩溃恢复后仍能判出"哪一轮才是当前轮"。</p>
*/
@Column(name = "apply_round")
private Integer applyRound;
/**
* 受理时规范载荷指纹(2026-10-25 T04,SHA-256 十六进制):同 requestToken 必须同指纹——
* 同 ID 不同载荷是两份命令,撞在同一个 token 上按冲突拒绝,不伪装成幂等成功。
* 存量行(指纹列为 NULL)跳过该核对(守卫缺省是关,与 token/round 存量纪律一致)。
*/
@Column(name = "apply_payload_hash", length = 64)
private String applyPayloadHash;
/**
* 事件序号计数器(2026-10-25 T05):实例内事件事实 eventSeq 的发号器。发号时在事务内
* 悲观锁住本行(findByIdForUpdate 当前读)再 +1——REPEATABLE READ 下对聚合做快照读会
* 拿到陈旧 max(FlowEngineIT 会签并发实证),行上计数器+行锁才是串行安全的。
*/
@Column(name = "event_seq_counter")
private Integer eventSeqCounter;
/**
* 台账展示位(F11-4):该办件是否「路由挂起」(审批中且最新 attempt 的路由判断行无出口)。
* 非持久列——由管理端办件分页(FlowAdminController.instancePage)按页查询后填充,
* 随实体序列化给前端画红标;业务代码不得读写该字段。
*/
@javax.persistence.Transient
private Boolean routeHung;
}