build / build-push (push) Failing after 55s
在 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 派生方法属另一实现线)
184 lines
8.1 KiB
Java
184 lines
8.1 KiB
Java
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;
|
||
}
|