Files
svc-hall-service/sql/fc_projection_and_apply_intent.sql
T
chenxing 391281582a
build / build-push (push) Successful in 1m21s
t1~t3+精修: 事项FC绑定/可信建件(意图+投影+恢复)/答卷对账Worker
含10-22精修: 对账有限重试(retry_count/指数退避/work_version防旧覆盖)+接缝身份(source-app-key空拒收)+应急对账闸让位FlowApplyRecoveryService+同attempt指令不重开终态。SQL未执行。
2026-10-22 10:03:07 +08:00

187 lines
15 KiB
SQL
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.
-- ============================================================
-- 办事大厅 · 可信建件 / 答卷投影 / 事件台账 / 出站命令 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(不回填)