-- ============================================================ -- 办事大厅 · 可信建件 / 答卷投影 / 事件台账 / 出站命令 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; -- ③不建物理外键:intent→service_apply、projection→intent 都是跨表逻辑引用, -- 回调/事件与用户请求并发写入时,FK 会把"先到的一方"变成死锁或顺序要求; -- ④唯一键承担全部收敛职责(应用层不允许靠时序判断"我该不该再建一次")。 -- 收敛口径:事件先到还是确认先到,结果都撞进同一组唯一键—— -- 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,不回填、不建双轨(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(不回填)