Files
form-center-service/sql/answer_delivery_v2.sql
chenxing d7473112b0
build / build-push (push) Successful in 1m26s
t1~t3落地+10-22精修: 消费授权v3(owner/grant/订阅/目录)+答卷投递v2(可信提交上下文/快照/幂等关联)+答卷对账v1(清单/派发/租约/结果)
含10-22精修六类修复: 续租renewRunning/结果INCOMPLETE_RESULT门/终态TASK_TERMINAL/liveGrant复用ACL/hosted幂等重放绑定/错误明细整条重序列化。
合规审计与证据: 工作区docs/tasks/REFINEMENT_AUDIT.md + evidence/refinement-20261022/(patch+指纹)。SQL未执行、测试未运行(仅-skipTests编译)。
2026-10-22 10:02:57 +08:00

106 lines
10 KiB
SQL
Raw Permalink 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.
-- ============================================================
-- 表单中心 v2 · 快照投递与可信提交上下文 DDL(MySQL 5.7 兼容复核版,TASK_2)
-- 范围:《answer-delivery-v2.md》§2/§3/§6 = answer.answer_revision 加列 + 建两张表
-- answer_delivery(outbox)/ submission_context(可信提交上下文)
-- 依据:任务书 TASK_2 B.1(单调修订号与不可变 outbox)、B.3(逐授权订阅一行)、
-- A.2/A.3(令牌摘要与意图↔答卷一对一原子关联)
-- 用途:生产 ddl-auto=none 时的升级脚本;开发期(本机 bsch_formcenter,ddl-auto=update)由
-- Hibernate 自动追加,本脚本同时是"实表应当长什么样"的评审权威(沿 v3/t16 先例)。
-- 5.7 复核要点:①无 CTE/WITH、无函数索引、无生成列、无 CHECK 约束;②utf8mb4 显式写
-- CHARSET/COLLATE;③<b>不建物理外键</b>——answer_uuid / form_uuid 都是逻辑引用
-- (答卷无物理删除但表单可软删,投递行与上下文必须能在其之后继续存在以支撑溯源);
-- ④唯一键里的 answer_revision 是 BIGINT,不占 utf8mb4 长度预算。
-- 身份口径:consumer_app_key / source_app_key 存<b>平台 appKey</b>(UserBase 注册分配、
-- 验签三件套里的 X-App-Key),<b>不是</b> form.app_code 那种人读 slug——
-- 资源 appCode 当消费者会把"同应用多消费方"合并成一行(任务书 B.3 点名禁止)。
-- 双时刻纪律:broker_accepted_at 与 consumer_applied_at 是两列、两个写入方,
-- 前者只由 confirm 回调写、后者只由签名回执端点写;任何一侧都不许替另一侧作证(B.4/B.6)。
-- 存量:answer.answer_revision 默认 0,<b>不回填历史版本、不建双轨</b>(任务书 A.5 末句)——
-- 存量行此后第一次变更取号 1,此前的对外快照本来就没有 hash 事实,回填等于伪造历史。
-- 非幂等:MySQL 5.7 无评审友好的 ADD/CREATE IF NOT EXISTS 写法(写了反而掩盖"表已存在但列不对")。
-- 执行前先 `SHOW TABLES LIKE 'answer_delivery'` / `'submission_context'` 确认不存在,
-- 并 `SHOW COLUMNS FROM answer LIKE 'answer_revision'` 确认列不存在。
-- ============================================================
ALTER TABLE `answer`
ADD COLUMN `answer_revision` BIGINT NOT NULL DEFAULT 0
COMMENT '对外快照修订号:单调递增,凡改变对外快照的路径(创建/值编辑/改状态/软删,含批量)在同一事务内 +1 并写 outbox;与钉版 version 是两个维度';
CREATE TABLE `answer_delivery` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`event_id` VARCHAR(36) NOT NULL COMMENT '信封事件 id(uuid):broker 相关键与消费端幂等键同一件',
`protocol_version` VARCHAR(32) NOT NULL COMMENT '固定 fc.answer.snapshot.v2(锁死 fc.canon.v1 哈希口径)',
`event_type` VARCHAR(16) NOT NULL COMMENT 'created / updated / tombstoned(值改与状态改同归 updated)',
`source_app_key` VARCHAR(64) DEFAULT NULL COMMENT '发布方应用身份(服务端配置,不接受消费方申报)',
`consumer_app_key` VARCHAR(64) NOT NULL COMMENT '投递目标:显式消费应用平台 appKey',
`consumer_school_id` BIGINT NOT NULL COMMENT '投递目标租户域(与 appKey 一起构成授权坐标,不是资源归属校)',
`grant_version` INT NOT NULL COMMENT '入队时刻的授权纪元;发布前再核一次,进位或撤销即不重放',
`form_uuid` VARCHAR(32) NOT NULL COMMENT '资源坐标(逻辑引用,不建 FK)',
`form_version` INT NOT NULL COMMENT '答卷钉住的 schema 版本(与 answer.version 同值)',
`answer_uuid` VARCHAR(32) NOT NULL COMMENT '答卷 uuid(对外身份,库内 id 不外露)',
`answer_revision` BIGINT NOT NULL COMMENT '本行对应的快照修订号(与 answer.answer_revision 同值)',
`is_deleted` TINYINT NOT NULL DEFAULT 0 COMMENT '墓碑标记;软删行的快照照常投递,靠它表达删除',
`content_hash` CHAR(64) NOT NULL COMMENT 'fc.canon.v1 下核心快照(剔除 intent)的 SHA-256 十六进制小写',
`occurred_at` DATETIME NOT NULL COMMENT '业务发生时刻(= 答卷 update_time;投递时间不在此列)',
`snapshot_json` LONGTEXT NOT NULL COMMENT '不可变载荷:该消费方看到的完整快照(含仅对其可见的 intent 关联块)',
`state` VARCHAR(16) NOT NULL DEFAULT 'PENDING' COMMENT 'PENDING / BROKER_ACCEPTED / FAILED / DEAD / GRANT_REVOKED',
`retry_count` INT NOT NULL DEFAULT 0 COMMENT '投递元数据:重试计数<b>不进快照、不参与哈希</b>',
`next_retry_at` DATETIME DEFAULT NULL COMMENT '退避到点才再扫(base×2^n 封顶 1h),DEAD 置 NULL 不再自动重投',
`broker_accepted_at` DATETIME DEFAULT NULL COMMENT '总线 confirm(ack=true) 时刻——只证明总线收下,不证明业务应用',
`consumer_applied_at` DATETIME DEFAULT NULL COMMENT '消费方签名回执落定时刻(只有回执端点写得了)',
`consumer_outcome` VARCHAR(16) DEFAULT NULL COMMENT 'APPLIED / IGNORED / CONFLICT;重复上报不同结论时保留首次',
`last_replay_operation_id` VARCHAR(64) DEFAULT NULL COMMENT '最近一次授权重投的操作幂等键(同键不重复刷新退避)',
`last_error` VARCHAR(500) DEFAULT NULL COMMENT '末次失败原因或回执 localRef(截断存)',
`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_delivery_event` (`event_id`),
UNIQUE KEY `uk_delivery_revision` (`answer_uuid`, `answer_revision`, `consumer_app_key`, `consumer_school_id`),
KEY `idx_delivery_scan` (`state`, `next_retry_at`),
KEY `idx_delivery_consumer` (`consumer_app_key`, `consumer_school_id`, `id`),
KEY `idx_delivery_answer` (`answer_uuid`, `answer_revision`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
COMMENT='答卷快照 outbox(v2 §3:与答卷同事务落库、逐授权订阅一行、双时刻分列;不提供任何删除方法)';
CREATE TABLE `submission_context` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`consumer_app_key` VARCHAR(64) NOT NULL COMMENT '消费应用平台 appKey(取自已验签的 yh.app.auth,非请求体申报)',
`consumer_school_id` BIGINT NOT NULL COMMENT '消费租户域(≥1;签发时即过 read+subscribe 两把授权)',
`intent_id` VARCHAR(64) NOT NULL COMMENT '消费方业务意图键(服务端生成;浏览器不可注入)',
`form_uuid` VARCHAR(32) NOT NULL COMMENT '钉定的表单 uuid(成对校验过版本行才落)',
`form_version` INT NOT NULL COMMENT '钉定的确切版本号(hosted 永不回落到当前发布版)',
`app_code` VARCHAR(64) NOT NULL COMMENT '解析出来的资源 appCode(提交时按它寻址,不再走 code 遮蔽)',
`code` VARCHAR(64) NOT NULL COMMENT '解析出来的表单 code(与 app_code 一起构成提交体坐标核对)',
`submitter_id` INT NOT NULL COMMENT '消费方服务端断言的填报人;兑换时与 CAS 会话逐字复核',
`token_digest` CHAR(64) NOT NULL COMMENT 'contextToken 的 SHA-256(<b>明文不入库</b>,只在签发响应里出现一次)',
`embed_origin` VARCHAR(200) NOT NULL COMMENT '可嵌入父页面精确 origin(服务端配置锁定,被嵌页 postMessage 的唯一目标)',
`callback_url` VARCHAR(500) NOT NULL COMMENT '消费方确认入口绝对 URL(服务端配置;浏览器传入的一律不采信)',
`expires_at` DATETIME NOT NULL COMMENT '有效期(默认 30 分钟,form-center.hosted-fill.token-ttl-minutes)',
`answer_uuid` VARCHAR(32) DEFAULT NULL COMMENT '答卷原子关联(提交事务内写;NULL=尚未填报。一个意图只挂一份答卷)',
`linked_revision` BIGINT DEFAULT NULL COMMENT '关联时刻的快照修订号(收据与投影合并的判据之一)',
`linked_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_ctx_token_digest` (`token_digest`),
UNIQUE KEY `uk_ctx_intent` (`consumer_app_key`, `intent_id`),
UNIQUE KEY `uk_ctx_answer` (`answer_uuid`),
KEY `idx_ctx_form` (`form_uuid`, `form_version`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
COMMENT='可信提交上下文(v2 §6:意图↔答卷一对一、令牌只存摘要、坐标与 origin/回调都在签发时锁死)';
-- 自验(执行后逐条核对):
-- SHOW CREATE TABLE answer_delivery\G -- 应含 uk_delivery_event / uk_delivery_revision 两个唯一键
-- SHOW CREATE TABLE submission_context\G -- 应含 uk_ctx_intent(同意图不新建第二行)与 uk_ctx_answer
-- SHOW COLUMNS FROM answer LIKE 'answer_revision'; -- 必须存在且 NOT NULL DEFAULT 0
-- SELECT COUNT(*) FROM information_schema.columns
-- WHERE table_schema=DATABASE() AND table_name IN ('answer_delivery','submission_context')
-- AND column_name REGEXP 'secret|token$|retry_policy'; -- 必须 0 行(密钥与明文令牌都不入库)
-- SELECT COUNT(*) FROM information_schema.referential_constraints
-- WHERE table_name IN ('answer_delivery','submission_context'); -- 必须 0 行(无物理外键)
-- SELECT COUNT(*) FROM answer WHERE answer_revision <> 0; -- 刚执行完必须 0(不回填历史)