build / build-push (push) Successful in 1m21s
含10-22精修: 对账有限重试(retry_count/指数退避/work_version防旧覆盖)+接缝身份(source-app-key空拒收)+应急对账闸让位FlowApplyRecoveryService+同attempt指令不重开终态。SQL未执行。
187 lines
15 KiB
SQL
187 lines
15 KiB
SQL
-- ============================================================
|
||
-- 办事大厅 · 可信建件 / 答卷投影 / 事件台账 / 出站命令 DDL(MySQL 5.7 兼容复核版,TASK_2)
|
||
-- 范围:《answer-delivery-v2.md》§6~§8 = service_apply 加两列 + 建四张表
|
||
-- fc_apply_intent / fc_answer_projection / fc_answer_inbox / flow_apply_command
|
||
-- 依据:任务书 TASK_2 A.1/A.2/A.5(意图唯一键与"一次意图一个办件")、B.5(投影与台账分表)、
|
||
-- C.1/C.2(出站命令持久化 nextRetry、轮次与请求标识)
|
||
-- 用途:生产 ddl-auto=none 时的升级脚本;开发期(本机 bsch_svchall,ddl-auto=update)由 Hibernate
|
||
-- 自动追加,本脚本同时是"实表应当长什么样"的评审权威(沿 v3/migrate_v2 先例)。
|
||
-- 5.7 复核要点:①无 CTE/函数索引/生成列/CHECK;②utf8mb4 显式 CHARSET/COLLATE;
|
||
-- ③<b>不建物理外键</b>:intent→service_apply、projection→intent 都是跨表逻辑引用,
|
||
-- 回调/事件与用户请求并发写入时,FK 会把"先到的一方"变成死锁或顺序要求;
|
||
-- ④唯一键承担全部收敛职责(应用层不允许靠时序判断"我该不该再建一次")。
|
||
-- 收敛口径:<b>事件先到还是确认先到,结果都撞进同一组唯一键</b>——
|
||
-- fc_apply_intent(sch_id,submitter_id,item_id,client_key) 挡重复 POST;
|
||
-- fc_apply_intent(fc 意图键) 挡重复上下文;fc_apply_intent(answer_uuid) 与 (apply_id)
|
||
-- 挡"一份答卷形成两个办件";fc_answer_projection(answer_uuid,sch_id) 挡一份答卷两份本地投影;
|
||
-- fc_answer_inbox(event_id,consumer_app_key) 挡同一封事件写两次;
|
||
-- flow_apply_command(apply_id,attempt) 挡一轮两条命令、(request_token) 挡标识复用。
|
||
-- 双时刻纪律:中心侧 broker_accepted / consumer_applied 分列;本侧 inbox 只留"我这边怎么处置"
|
||
-- 与"回执上报状态",两边都不许替对方作证。
|
||
-- 存量:service_apply 两个新列对历史行恒 NULL,<b>不回填、不建双轨</b>(A.5 末句"新数据可追溯")。
|
||
-- 非幂等:执行前先 SHOW TABLES LIKE 逐张确认不存在,并 SHOW COLUMNS FROM service_apply LIKE 'fc_intent_id'。
|
||
-- ============================================================
|
||
|
||
ALTER TABLE `service_apply`
|
||
ADD COLUMN `fc_intent_id` INT DEFAULT NULL
|
||
COMMENT '可信建件意图 id(fc_apply_intent.id);NULL=旧链路/无表单事项直接提交。唯一性判据在意图侧 uk_intent_apply,本列只作反向可查',
|
||
ADD COLUMN `fc_answer_uuid` VARCHAR(32) DEFAULT NULL
|
||
COMMENT '建件依据的答卷 uuid(表单中心对外身份);旧链路恒 NULL';
|
||
|
||
CREATE TABLE `fc_apply_intent` (
|
||
`id` INT NOT NULL AUTO_INCREMENT,
|
||
`sch_id` BIGINT NOT NULL COMMENT '租户域(会话推导,浏览器不可注入)',
|
||
`site_code` VARCHAR(64) NOT NULL COMMENT '事项坐标之一(CMS 站点编码)',
|
||
`item_id` INT NOT NULL COMMENT 'service_item.id(逻辑引用,不建 FK)',
|
||
`item_code` VARCHAR(64) NOT NULL COMMENT '事项编码快照',
|
||
`item_title` VARCHAR(200) NOT NULL COMMENT '事项标题快照',
|
||
`submitter_id` INT NOT NULL COMMENT '提交者(CAS 会话)',
|
||
`submitter_name` VARCHAR(128) NOT NULL COMMENT '提交者姓名快照',
|
||
`client_key` VARCHAR(64) NOT NULL COMMENT '客户端幂等键(重复 POST 回到同一意图)',
|
||
`fc_form_uuid` VARCHAR(32) NOT NULL COMMENT '绑定表单中心 uuid(TASK_1 成对坐标之一)',
|
||
`fc_form_version` INT NOT NULL COMMENT '绑定表单中心确切版本(单传或改值都进不了这条链)',
|
||
`flow_guid` VARCHAR(32) DEFAULT NULL COMMENT '绑定流程线(发起前判过就绪;建件时快照进办件)',
|
||
`consumer_app_key` VARCHAR(64) NOT NULL COMMENT '本应用出站 appKey(=中心侧上下文归属)',
|
||
`fc_intent_id` VARCHAR(64) NOT NULL COMMENT '送给中心的业务意图键(自然键哈希,稳定而非随机;与本地自增 id 不是一回事)',
|
||
`context_token` VARCHAR(64) DEFAULT NULL COMMENT '中心签发的短时提交上下文令牌(交给被嵌页用,故明文存;30 分钟失效)',
|
||
`embed_origin` VARCHAR(200) DEFAULT NULL COMMENT '中心锁定的可嵌入父页面 origin(不是本地拼的)',
|
||
`fill_page_url` VARCHAR(500) DEFAULT NULL COMMENT '被嵌填报页地址(服务端按配置基址拼,浏览器给的路径不采信)',
|
||
`state` VARCHAR(16) NOT NULL DEFAULT 'ISSUED' COMMENT 'ISSUED / ANSWERED / APPLIED / VOID',
|
||
`expires_at` DATETIME DEFAULT NULL COMMENT '令牌有效期(过期即 VOID,不无限期占着可提交凭据)',
|
||
`answer_uuid` VARCHAR(32) DEFAULT NULL COMMENT '收据/事件确认的答卷 uuid(唯一:一意图一答卷)',
|
||
`answer_revision` BIGINT DEFAULT NULL COMMENT '确认时刻的快照修订号',
|
||
`content_hash` CHAR(64) DEFAULT NULL COMMENT 'fc.canon.v1 快照哈希',
|
||
`apply_id` INT DEFAULT NULL COMMENT '形成的本地办件(唯一:一次意图最多一个办件)',
|
||
`confirmed_at` DATETIME DEFAULT NULL COMMENT '服务端收据确认时刻(不是浏览器报的成功时刻)',
|
||
`create_by` VARCHAR(255) DEFAULT NULL,
|
||
`create_time` DATETIME DEFAULT NULL,
|
||
`update_by` VARCHAR(255) DEFAULT NULL,
|
||
`update_time` DATETIME DEFAULT NULL,
|
||
PRIMARY KEY (`id`),
|
||
UNIQUE KEY `uk_intent_client` (`sch_id`, `submitter_id`, `item_id`, `client_key`),
|
||
UNIQUE KEY `uk_intent_fc` (`consumer_app_key`, `fc_intent_id`),
|
||
UNIQUE KEY `uk_intent_answer` (`answer_uuid`),
|
||
UNIQUE KEY `uk_intent_apply` (`apply_id`),
|
||
KEY `idx_intent_user` (`sch_id`, `submitter_id`, `state`),
|
||
KEY `idx_intent_expire` (`state`, `expires_at`)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
|
||
COMMENT='可信建件意图(A.1/A.2/A.5:浏览器只交坐标+幂等键,建件唯一性由本表唯一键保证)';
|
||
|
||
CREATE TABLE `fc_answer_projection` (
|
||
`id` INT NOT NULL AUTO_INCREMENT,
|
||
`sch_id` BIGINT NOT NULL COMMENT '消费租户域(与 answer_uuid 一起构成唯一投影键)',
|
||
`answer_uuid` VARCHAR(32) NOT NULL COMMENT '答卷 uuid',
|
||
`form_uuid` VARCHAR(32) NOT NULL COMMENT '关系坐标:表单 uuid(A.5 要求关系上可追溯)',
|
||
`form_version` INT NOT NULL COMMENT '关系坐标:钉住的 schema 版本',
|
||
`revision` BIGINT NOT NULL COMMENT '中心侧单调快照修订号(低版本忽略、同版比 hash)',
|
||
`projection_version` BIGINT NOT NULL DEFAULT 0 COMMENT 'Local optimistic generation; independent of FC revision',
|
||
`content_hash` CHAR(64) NOT NULL COMMENT '中心哈希;本地按 fc.canon.v1 复算必须逐字一致',
|
||
`is_deleted` TINYINT NOT NULL DEFAULT 0 COMMENT '墓碑(本地保留 revision,旧 created 复活不了它)',
|
||
`app_code` VARCHAR(64) DEFAULT NULL,
|
||
`code` VARCHAR(64) DEFAULT NULL,
|
||
`submitter_id` INT DEFAULT NULL,
|
||
`subject_id` INT DEFAULT NULL,
|
||
`status` VARCHAR(64) DEFAULT NULL COMMENT '答卷轻状态位(中心改状态会推新 revision 过来)',
|
||
`values_json` LONGTEXT DEFAULT NULL COMMENT '值文档全文(B.6:每条事件不回查答案值文档)',
|
||
`answer_no` VARCHAR(64) DEFAULT NULL,
|
||
`answer_create_time` VARCHAR(32) DEFAULT NULL COMMENT '中心给的 ISO 形态原文,不做本地时区换算',
|
||
`answer_update_time` VARCHAR(32) DEFAULT NULL,
|
||
`intent_id` INT DEFAULT NULL COMMENT '关联意图(唯一:一份投影只挂一个意图;NULL=事件先到的未关联态)',
|
||
`apply_id` INT DEFAULT NULL COMMENT '已形成的办件(可查回填,收敛判据在意图侧)',
|
||
`flow_guid` VARCHAR(32) DEFAULT NULL COMMENT '关系坐标:流程线(A.5 点名要留)',
|
||
`link_state` VARCHAR(16) NOT NULL DEFAULT 'UNLINKED' COMMENT 'UNLINKED / LINKED / APPLIED',
|
||
`conflict_flag` TINYINT NOT NULL DEFAULT 0 COMMENT '1=同 revision 异 hash,冲突待核验(不覆盖)',
|
||
`conflict_detail` VARCHAR(500) DEFAULT NULL COMMENT '冲突/退役留证',
|
||
`last_event_id` VARCHAR(36) DEFAULT NULL COMMENT '最近落库的事件 id(审计对应关系)',
|
||
`biz_ext_json` LONGTEXT DEFAULT NULL COMMENT '业务端扩展列:中心快照永不写(B.5)',
|
||
`create_by` VARCHAR(255) DEFAULT NULL,
|
||
`create_time` DATETIME DEFAULT NULL,
|
||
`update_by` VARCHAR(255) DEFAULT NULL,
|
||
`update_time` DATETIME DEFAULT NULL,
|
||
PRIMARY KEY (`id`),
|
||
UNIQUE KEY `uk_projection_answer` (`answer_uuid`, `sch_id`),
|
||
UNIQUE KEY `uk_projection_intent` (`intent_id`),
|
||
KEY `idx_projection_state` (`sch_id`, `form_uuid`, `revision`),
|
||
KEY `idx_projection_apply` (`apply_id`)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
|
||
COMMENT='答卷在本应用的唯一投影 + 意图↔答卷↔办件的独立唯一关联表(A.5/B.5)';
|
||
|
||
CREATE TABLE `fc_answer_inbox` (
|
||
`id` INT NOT NULL AUTO_INCREMENT,
|
||
`event_id` VARCHAR(36) NOT NULL COMMENT '信封事件 id',
|
||
`protocol_version` VARCHAR(32) NOT NULL COMMENT '固定 fc.answer.snapshot.v2',
|
||
`event_type` VARCHAR(16) NOT NULL COMMENT 'created / updated / tombstoned',
|
||
`consumer_app_key` VARCHAR(64) NOT NULL COMMENT '本应用 appKey(信封目标必须等于它)',
|
||
`consumer_school_id` BIGINT NOT NULL COMMENT '信封目标租户域',
|
||
`source_app_key` VARCHAR(64) DEFAULT NULL COMMENT '发布方身份',
|
||
`grant_version` INT DEFAULT NULL COMMENT '信封带的授权纪元(与本地核验快照比对,旧纪元不写)',
|
||
`form_uuid` VARCHAR(32) DEFAULT NULL,
|
||
`form_version` INT DEFAULT NULL,
|
||
`answer_uuid` VARCHAR(32) DEFAULT NULL,
|
||
`revision` BIGINT DEFAULT NULL,
|
||
`content_hash` CHAR(64) DEFAULT NULL,
|
||
`is_deleted` TINYINT NOT NULL DEFAULT 0,
|
||
`occurred_at` VARCHAR(40) DEFAULT NULL COMMENT '中心给的 occurredAt 原文(投递时间不进哈希)',
|
||
`state` VARCHAR(16) NOT NULL COMMENT 'APPLIED / IGNORED / CONFLICT / DEFERRED / REJECTED',
|
||
`detail` VARCHAR(500) DEFAULT NULL COMMENT '未应用原因(撤权暂缓/低版本/校验失败要点)',
|
||
`report_state` VARCHAR(16) NOT NULL DEFAULT 'PENDING' COMMENT '回执上报:PENDING / SENT / FAILED',
|
||
`report_attempts` INT NOT NULL DEFAULT 0,
|
||
`next_report_at` DATETIME DEFAULT NULL,
|
||
`reported_at` DATETIME DEFAULT NULL,
|
||
`received_at` DATETIME NOT NULL COMMENT '本地接收时刻(与中心 occurredAt 不是一回事)',
|
||
`create_by` VARCHAR(255) DEFAULT NULL,
|
||
`create_time` DATETIME DEFAULT NULL,
|
||
`update_by` VARCHAR(255) DEFAULT NULL,
|
||
`update_time` DATETIME DEFAULT NULL,
|
||
PRIMARY KEY (`id`),
|
||
UNIQUE KEY `uk_inbox_event` (`event_id`, `consumer_app_key`),
|
||
KEY `idx_inbox_answer` (`answer_uuid`, `revision`),
|
||
KEY `idx_inbox_report` (`report_state`, `next_report_at`)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
|
||
COMMENT='事件接收台账(B.5/B.6:投影存最新事实,本表存每封怎么处置的,不提供删除方法)';
|
||
|
||
CREATE TABLE `flow_apply_command` (
|
||
`id` INT NOT NULL AUTO_INCREMENT,
|
||
`apply_id` INT NOT NULL COMMENT 'service_apply.id(业务键 bizId 的数值部分)',
|
||
`sch_id` BIGINT NOT NULL,
|
||
`site_code` VARCHAR(64) NOT NULL,
|
||
`biz_type` VARCHAR(32) NOT NULL COMMENT '与中心 uk 一致的业务类型键(svc-hall)',
|
||
`biz_id` VARCHAR(64) NOT NULL COMMENT '业务键=办件 id(与旧链路逐字相同,回调解析不变)',
|
||
`attempt` INT NOT NULL DEFAULT 1 COMMENT '轮次:仅用户明确授权的重提才 +1',
|
||
`request_token` VARCHAR(64) NOT NULL COMMENT '本次命令标识:同 token 重投=同一命令,中心不重启推进',
|
||
`flow_guid` VARCHAR(32) NOT NULL,
|
||
`payload_json` LONGTEXT NOT NULL COMMENT '出站载荷快照(重放不回读办件行)',
|
||
`state` VARCHAR(16) NOT NULL DEFAULT 'PENDING' COMMENT 'PENDING / UNKNOWN / DONE / FAILED / DEAD',
|
||
`retry_count` INT NOT NULL DEFAULT 0,
|
||
`next_retry_at` DATETIME DEFAULT NULL COMMENT '持久化退避到点(C.1 崩溃恢复就靠这一列)',
|
||
`remote_instance_id` BIGINT DEFAULT NULL,
|
||
`remote_status` INT DEFAULT NULL COMMENT '查/发起得到的远端真实状态(先回填再决定,不把 30/40 当重试)',
|
||
`remote_version` INT DEFAULT NULL,
|
||
`last_error` VARCHAR(500) DEFAULT NULL,
|
||
`sent_at` DATETIME DEFAULT NULL,
|
||
`settled_at` DATETIME DEFAULT NULL,
|
||
`intent_id` INT DEFAULT NULL COMMENT '可信建件意图(旧链路为 NULL,不回填历史)',
|
||
`create_by` VARCHAR(255) DEFAULT NULL,
|
||
`create_time` DATETIME DEFAULT NULL,
|
||
`update_by` VARCHAR(255) DEFAULT NULL,
|
||
`update_time` DATETIME DEFAULT NULL,
|
||
PRIMARY KEY (`id`),
|
||
UNIQUE KEY `uk_command_attempt` (`apply_id`, `attempt`),
|
||
UNIQUE KEY `uk_command_token` (`request_token`),
|
||
KEY `idx_command_scan` (`state`, `next_retry_at`),
|
||
KEY `idx_command_biz` (`sch_id`, `biz_type`, `biz_id`)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
|
||
COMMENT='出站发起命令(C.1/C.2:HTTP 不占长事务,未知态先查后发,进程崩溃后按 next_retry_at 恢复)';
|
||
|
||
-- 落地校对项(执行前后各核一遍,别靠"看起来一样"):
|
||
-- 1) 注:fc_apply_intent.fc_intent_id 是"送给中心的意图键"(VARCHAR),
|
||
-- 与同表自增主键 id(INT,被 fc_answer_projection.intent_id 引用)是两个东西,
|
||
-- service_apply.fc_intent_id 引用的才是后者(INT)——两侧列名同名不同型是刻意的历史命名,
|
||
-- 评审时按"哪张表的外侧"读,不要互换。
|
||
-- 2) SHOW CREATE TABLE fc_apply_intent\G -- 四个唯一键都在
|
||
-- 3) SHOW CREATE TABLE fc_answer_projection\G -- uk_projection_answer + uk_projection_intent
|
||
-- 4) SELECT COUNT(*) FROM information_schema.referential_constraints
|
||
-- WHERE table_name IN ('fc_apply_intent','fc_answer_projection','fc_answer_inbox','flow_apply_command');
|
||
-- -- 必须 0 行(无物理外键)
|
||
-- 5) SELECT COUNT(*) FROM service_apply WHERE fc_intent_id IS NOT NULL; -- 刚执行完必须 0(不回填)
|