t1~t3落地+10-22精修: 消费授权v3(owner/grant/订阅/目录)+答卷投递v2(可信提交上下文/快照/幂等关联)+答卷对账v1(清单/派发/租约/结果)
build / build-push (push) Successful in 1m26s
build / build-push (push) Successful in 1m26s
含10-22精修六类修复: 续租renewRunning/结果INCOMPLETE_RESULT门/终态TASK_TERMINAL/liveGrant复用ACL/hosted幂等重放绑定/错误明细整条重序列化。 合规审计与证据: 工作区docs/tasks/REFINEMENT_AUDIT.md + evidence/refinement-20261022/(patch+指纹)。SQL未执行、测试未运行(仅-skipTests编译)。
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
-- ============================================================
|
||||
-- 表单中心 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(不回填历史)
|
||||
@@ -0,0 +1,128 @@
|
||||
-- ============================================================
|
||||
-- 表单中心 · 差异对账任务 DDL(MySQL 5.7 兼容复核版,TASK_3)
|
||||
-- 范围:answer-reconcile-v1 §2/§3/§5 = 三张新表
|
||||
-- answer_reconcile_task(任务)/ answer_reconcile_manifest_row(不可变清单)/
|
||||
-- answer_reconcile_result(结果回报)
|
||||
-- 依据:任务书 TASK_3 改动点 1/2/4/8;不复用 answer_op_log 当任务表、不动 answer/answer_delivery。
|
||||
-- 用途:生产 ddl-auto=none 时的升级脚本;开发期(本机 bsch_formcenter,ddl-auto=update)由
|
||||
-- Hibernate 自动追加,本脚本同时是"实表应当长什么样"的评审权威(沿 v2/v3 先例)。
|
||||
-- 5.7 复核要点:①无 CTE/WITH、无函数索引、无生成列、无 CHECK 约束;②utf8mb4 显式写 CHARSET/COLLATE;
|
||||
-- ③<b>不建物理外键</b>——task_uuid 是逻辑引用,清单与结果要能在任务行之后继续存在以支撑复盘;
|
||||
-- ④清单游标与本地扫描游标同为 answer_uuid(32 位小写十六进制),general_ci 下与 binary 序一致。
|
||||
-- 身份口径:consumer_app_key 存平台 appKey(验签三件套里的 X-App-Key),不是 form.app_code 那种人读 slug。
|
||||
-- 审计纪律:三张表都<b>没有删除路径</b>(服务层与仓库层都不提供 delete),终态留表供复盘。
|
||||
-- 非幂等:MySQL 5.7 无评审友好的 CREATE TABLE IF NOT EXISTS 写法(写了反而掩盖"表已存在但列不对")。
|
||||
-- 执行前先 `SHOW TABLES LIKE 'answer_reconcile%'` 确认三张表都不存在。
|
||||
-- ============================================================
|
||||
|
||||
CREATE TABLE `answer_reconcile_task` (
|
||||
`id` BIGINT NOT NULL AUTO_INCREMENT,
|
||||
`task_uuid` VARCHAR(36) NOT NULL COMMENT '任务对外身份(服务端 UUID,≤36 字符;URL 与指令都用它)',
|
||||
`consumer_app_key` VARCHAR(64) NOT NULL COMMENT '对账目标的消费应用平台 appKey(<b>不是</b> app_code slug)',
|
||||
`consumer_school_id` BIGINT NOT NULL COMMENT '消费租户域:与投递行同口径,也就是答卷归属校',
|
||||
`form_uuid` VARCHAR(32) NOT NULL COMMENT '本轮范围=一张表单;全量=按生效订阅 fission 成多行任务',
|
||||
`form_version` INT NOT NULL COMMENT '物化时核验过的发布版本(缺席答复与修复信封的 formVersion 权威值)',
|
||||
`trigger_type` VARCHAR(16) NOT NULL COMMENT 'SCHEDULE / MANUAL(列名避开 MySQL 保留字 TRIGGER)',
|
||||
`operator` VARCHAR(64) DEFAULT NULL COMMENT '人工触发时的操作人(调度产生的为 NULL)',
|
||||
`idempotency_key` VARCHAR(128) NOT NULL COMMENT '日期+订阅+范围唯一键:调度重复跑不出第二行任务',
|
||||
`state` VARCHAR(16) NOT NULL DEFAULT 'QUEUED' COMMENT 'QUEUED/DISPATCHED/RUNNING/COMPLETED/PARTIAL/FAILED/CANCELLED',
|
||||
`manifest_state` VARCHAR(16) NOT NULL DEFAULT 'PENDING' COMMENT 'PENDING/MATERIALIZING/COMPLETE/FAILED:派发只在 COMPLETE 后',
|
||||
`manifest_complete` TINYINT NOT NULL DEFAULT 0 COMMENT '清单结束标记:<b>只有它为 1</b>,"清单里没有"才可能是权威缺席',
|
||||
`total_rows` INT NOT NULL DEFAULT 0 COMMENT '物化完成后=清单行数(上限见 fc.answer.reconcile.max-rows)',
|
||||
`total_pages` INT NOT NULL DEFAULT 0 COMMENT '确定总页(ceil(total_rows/page_size),不是"某页空了")',
|
||||
`materialized_rows` INT NOT NULL DEFAULT 0 COMMENT '已写入清单行数(进度与断点续作的计数)',
|
||||
`materialized_pages` INT NOT NULL DEFAULT 0 COMMENT '已物化页数',
|
||||
`checkpoint_cursor` VARCHAR(32) DEFAULT NULL COMMENT '物化检查点:已写到的最后一个 answer_uuid(重启从这里续)',
|
||||
`window_start` DATETIME NOT NULL COMMENT '任务起点=一致读事务开始时刻;晚于此刻的本地行不得判为多余行',
|
||||
`window_end` DATETIME DEFAULT NULL COMMENT '清单物化完成时刻(窗口长度给运维复盘用)',
|
||||
`grant_version` INT NOT NULL DEFAULT 1 COMMENT '物化时刻的授权纪元;每轮读取再核一次,进位即按撤权处置',
|
||||
`attempt` INT NOT NULL DEFAULT 0 COMMENT '派发尝试次数(首派 0;租约超期或派发失败后 +1,有上限)',
|
||||
`lease_until` DATETIME DEFAULT NULL COMMENT '派发/物化租约到期时刻:超期由扫描按 attempt+1 有限退避接手',
|
||||
`next_dispatch_at` DATETIME DEFAULT NULL COMMENT '退避到点才再派(base×2^n 封顶),耗尽即 FAILED',
|
||||
`dispatched_at` DATETIME DEFAULT NULL COMMENT '对端 2xx 收下指令的时刻(<b>不代表执行完成</b>,终态只看 result 回报)',
|
||||
`finished_at` DATETIME DEFAULT NULL COMMENT '终态落定时刻',
|
||||
`cancel_requested` TINYINT NOT NULL DEFAULT 0 COMMENT '取消请求:置 1 后清单/快照读取一律以取消口径答复,消费端据此停止剩余修复',
|
||||
`error_code` VARCHAR(32) DEFAULT NULL COMMENT '稳定错误码(GRANT_REVOKED/RESOURCE_LIMIT/MANIFEST_INCOMPLETE/…)',
|
||||
`error_detail` VARCHAR(500) 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_reconcile_task_uuid` (`task_uuid`),
|
||||
UNIQUE KEY `uk_reconcile_task_idem` (`idempotency_key`),
|
||||
KEY `idx_reconcile_dispatch` (`state`, `next_dispatch_at`),
|
||||
KEY `idx_reconcile_consumer` (`consumer_app_key`, `consumer_school_id`, `id`),
|
||||
KEY `idx_reconcile_coord` (`consumer_app_key`, `consumer_school_id`, `form_uuid`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
|
||||
COMMENT='差异对账任务(TASK_3 §2:一行=一个 (消费应用, 消费租户域, 表单) 坐标上的一次对账企图;不是投递行、不是答卷、不是 op_log)';
|
||||
|
||||
CREATE TABLE `answer_reconcile_manifest_row` (
|
||||
`id` BIGINT NOT NULL AUTO_INCREMENT,
|
||||
`task_id` BIGINT NOT NULL COMMENT '逻辑引用 answer_reconcile_task.id(不建物理外键)',
|
||||
`task_uuid` VARCHAR(36) NOT NULL COMMENT '任务身份冗余一份:清单行脱离任务上下文也要能自证是哪一轮',
|
||||
`form_uuid` VARCHAR(32) NOT NULL COMMENT '物化时核验过的表单坐标',
|
||||
`form_version` INT NOT NULL COMMENT '该答卷钉住的 schema 版本(= answer.version,不从旧清单猜版本)',
|
||||
`answer_uuid` VARCHAR(32) NOT NULL COMMENT '答卷 uuid:清单游标与消费端本地游标同为它',
|
||||
`revision` BIGINT NOT NULL COMMENT '物化时刻的对外修订号(含 0:存量未变更行原样入清单,修复端点会拒绝并只报告)',
|
||||
`content_hash` CHAR(64) NOT NULL COMMENT 'fc.canon.v1 摘要(与投递行同一条规范化实现,本表不复制算法)',
|
||||
`is_deleted` TINYINT NOT NULL DEFAULT 0 COMMENT '墓碑也进清单:"缺席"与"存在但已删"是两个不同结论',
|
||||
`seq_no` INT NOT 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_manifest_row` (`task_id`, `answer_uuid`),
|
||||
KEY `idx_manifest_page` (`task_id`, `answer_uuid`),
|
||||
KEY `idx_manifest_seq` (`task_id`, `seq_no`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
|
||||
COMMENT='任务不可变清单(TASK_3 §3:一致读事务内物化四元摘要含墓碑;此后分页只读它,全列 updatable=false,无 update/delete 方法)';
|
||||
|
||||
CREATE TABLE `answer_reconcile_result` (
|
||||
`id` BIGINT NOT NULL AUTO_INCREMENT,
|
||||
`task_id` BIGINT NOT NULL COMMENT '逻辑引用 answer_reconcile_task.id',
|
||||
`task_uuid` VARCHAR(36) NOT NULL COMMENT '任务身份(回报定位用)',
|
||||
`consumer_app_key` VARCHAR(64) NOT NULL COMMENT '回报来源:<b>必须等于已验签身份</b>,自报不符即拒',
|
||||
`consumer_school_id` BIGINT NOT NULL COMMENT '消费租户域(跨校回报的判据之一)',
|
||||
`form_uuid` VARCHAR(32) NOT NULL COMMENT '范围坐标(每条错误与计数都能定位到这里)',
|
||||
`attempt` INT NOT NULL COMMENT '对应的派发尝试;比最新 attempt 更旧的回报不推进状态(不倒退新 attempt)',
|
||||
`state` VARCHAR(16) NOT NULL COMMENT 'COMPLETED / PARTIAL / FAILED / CANCELLED / RUNNING(进度回报)',
|
||||
`local_scan_complete` TINYINT NOT NULL DEFAULT 0 COMMENT '消费端本地扫描是否整轮完成(多余行判定的前提之一)',
|
||||
`manifest_complete` TINYINT NOT NULL DEFAULT 0 COMMENT '消费端实际读到的中心结束标记(与中心不一致则本轮结论不成立)',
|
||||
`scanned_rows` INT NOT NULL DEFAULT 0 COMMENT '比较过的答卷数',
|
||||
`pages_processed` INT NOT NULL DEFAULT 0 COMMENT '处理页数',
|
||||
`missing_repaired` INT NOT NULL DEFAULT 0 COMMENT '缺失修复数(repairAuthoritative=APPLIED,期望三元组全 null)',
|
||||
`stale_repaired` INT NOT NULL DEFAULT 0 COMMENT '陈旧修复数(本地 revision 更低)',
|
||||
`hash_conflicts` INT NOT NULL DEFAULT 0 COMMENT '同 revision 异 hash:记录后按中心当前值修复',
|
||||
`tombstones_repaired` INT NOT NULL DEFAULT 0 COMMENT '墓碑修复数(同一权威修复原语,保留本地业务扩展)',
|
||||
`local_ahead` INT NOT NULL DEFAULT 0 COMMENT '本地 revision 更高:只向中心复核,禁止降级覆盖',
|
||||
`deferred` INT NOT NULL DEFAULT 0 COMMENT '授权核验不可达/撤权/纪元不等而暂停的条目',
|
||||
`extra_reported` INT NOT NULL DEFAULT 0 COMMENT '只报告的多余行(含期间新增、createTime 未知、范围不明)',
|
||||
`extra_retired` INT NOT NULL DEFAULT 0 COMMENT '经 FC 签名缺席证明实际退役的行数(无证明即不增)',
|
||||
`error_count` INT NOT NULL DEFAULT 0 COMMENT '错误条目总数(超上限会截断,明细以 errors_json 为准)',
|
||||
`errors_json` MEDIUMTEXT NOT NULL COMMENT '错误明细(有上限、截断存;每条含 consumer/task/range/answer 四坐标,<b>不含 values</b>)',
|
||||
`resumed` TINYINT NOT NULL DEFAULT 0 COMMENT '1=进程重启后从检查点续跑的同一 attempt 再报',
|
||||
`reported_at` DATETIME NOT 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_result_attempt` (`task_id`, `attempt`),
|
||||
KEY `idx_result_task` (`task_id`, `attempt`),
|
||||
KEY `idx_result_consumer` (`consumer_app_key`, `consumer_school_id`, `id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
|
||||
COMMENT='对账结果回报(TASK_3 §5:(task_id, attempt) 唯一=幂等锚点;一 attempt 一行不覆盖,APPLIED 类计数不代表建件或流程成功)';
|
||||
|
||||
-- 自验(执行后逐条核对):
|
||||
-- SHOW CREATE TABLE answer_reconcile_task\G -- 应含 uk_reconcile_task_idem(同日同订阅同范围只一行)
|
||||
-- SHOW CREATE TABLE answer_reconcile_manifest_row\G -- 应含 uk_manifest_row(同任务同答卷不重复入清单)
|
||||
-- SHOW CREATE TABLE answer_reconcile_result\G -- 应含 uk_result_attempt(重复回报幂等吸收的库级形态)
|
||||
-- SELECT COUNT(*) FROM information_schema.referential_constraints
|
||||
-- WHERE table_name IN ('answer_reconcile_task','answer_reconcile_manifest_row','answer_reconcile_result');
|
||||
-- -- 必须 0 行(无物理外键)
|
||||
-- SELECT COUNT(*) FROM information_schema.columns
|
||||
-- WHERE table_schema=DATABASE() AND table_name='answer_reconcile_result'
|
||||
-- AND column_name IN ('values_json','snapshot_json','secret','token');
|
||||
-- -- 必须 0 行(值文档与密钥不进对账留痕)
|
||||
@@ -0,0 +1,64 @@
|
||||
-- ============================================================
|
||||
-- 表单中心 v3 · 消费应用授权 DDL(MySQL 5.7 兼容复核版,TASK_1)
|
||||
-- 范围:《consumer-access-v3.md》§2/§3 = 建两张表 form_ownership / form_consumer_grant
|
||||
-- 依据:VERIFICATION V03/V04/V07;owner 绑定与 grant 分权(读/订阅)、默认拒绝、撤销留痕
|
||||
-- 用途:生产 ddl-auto=none 时的升级脚本;开发期(本机 bsch_formcenter,ddl-auto=update)由 Hibernate
|
||||
-- 自动追加,本脚本同时是"实表应当长什么样"的评审权威(沿 t2/t3/t10/t16 先例)。
|
||||
-- 5.7 复核要点:①无 CTE/WITH、无函数索引、无生成列、无 CHECK 约束;②utf8mb4 显式写 CHARSET/COLLATE;
|
||||
-- ③<b>不建物理外键</b>——form_uuid 对 form 是逻辑引用(同 t16 对 app_code 的处理:
|
||||
-- 表单软删是唯一切除形态且无物理删除,绑定/授权行必须能在表单软删后继续存在以支撑溯源,
|
||||
-- 建 FK 等于把"级联删"写进库侧规则);④无可空业务列(判权路径不写"未知=怎样"的分支)。
|
||||
-- 身份口径:owner_app_key / consumer_app_key 存的是<b>平台 appKey</b>(UserBase 应用注册分配,
|
||||
-- 验签三件套里那个 X-App-Key),<b>不是</b> form.app_code 那种人读 slug。
|
||||
-- app_registry 仍是快应用目录,本批一行不加(v3 §1 红线:不把业务授权塞回注册表)。
|
||||
-- 租户口径:consumer_school_id 既是授权生效域,也是读答卷时的行集限定域;0 不是合法消费域
|
||||
-- (平台级是资源域,不是一个能答卷的租户),服务侧直接拒。
|
||||
-- 默认拒绝:两把 *_allowed 开关默认 0,enabled 默认 1(建行即生效,但建之前服务侧必须显式给开关);
|
||||
-- 撤销是 enabled=0 + grant_version 进位,<b>不删行</b>(判权 SQL 必须带 enabled 条件)。
|
||||
-- 非幂等:MySQL 5.7 无 ADD/CREATE IF NOT EXISTS 的评审友好写法(写了反而掩盖"表已存在但列不对"的漂移)。
|
||||
-- 执行前先 `SHOW TABLES LIKE 'form_ownership'` / `'form_consumer_grant'` 确认不存在;
|
||||
-- 存量:系统未上线,两张表零存量、无需回填(与 t16 同一论证)。
|
||||
-- ============================================================
|
||||
|
||||
CREATE TABLE `form_ownership` (
|
||||
`id` BIGINT NOT NULL AUTO_INCREMENT,
|
||||
`form_uuid` VARCHAR(32) NOT NULL COMMENT '资源坐标:表单全局 uuid(契约 §1,不可枚举、跨版本不变)',
|
||||
`form_school_id` BIGINT NOT NULL COMMENT '绑定时表单自己的 sch_id;0=平台级表单',
|
||||
`owner_app_key` VARCHAR(64) NOT NULL COMMENT '资源拥有者应用的平台 appKey(写入前已核验 UserBase 注册身份)',
|
||||
`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_ownership_form` (`form_uuid`),
|
||||
KEY `idx_ownership_owner` (`owner_app_key`, `form_school_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
|
||||
COMMENT='表单资源拥有者绑定(v3 §2:一张表一个 owner,只能由管理面写;form_uuid 为逻辑引用不建 FK)';
|
||||
|
||||
CREATE TABLE `form_consumer_grant` (
|
||||
`id` BIGINT NOT NULL AUTO_INCREMENT,
|
||||
`form_uuid` VARCHAR(32) NOT NULL COMMENT '被授权的表单 uuid',
|
||||
`consumer_school_id` BIGINT NOT NULL COMMENT '消费租户(授权生效域 = 读答卷的行集限定域,≥1)',
|
||||
`consumer_app_key` VARCHAR(64) NOT NULL COMMENT '消费应用的平台 appKey',
|
||||
`read_allowed` TINYINT NOT NULL DEFAULT 0 COMMENT '读授权:目录/schema/answer/answers;默认拒绝',
|
||||
`subscribe_allowed` TINYINT NOT NULL DEFAULT 0 COMMENT '订阅授权:答卷变更事件投递(owner 不天然持有)',
|
||||
`enabled` TINYINT NOT NULL DEFAULT 1 COMMENT '生效开关;0=已撤销(保留行供审计与 grant_version 判新旧)',
|
||||
`grant_version` INT NOT NULL DEFAULT 1 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_grant` (`form_uuid`, `consumer_school_id`, `consumer_app_key`),
|
||||
KEY `idx_grant_consumer` (`consumer_app_key`, `consumer_school_id`, `enabled`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
|
||||
COMMENT='消费应用对某张表单的显式授权(v3 §2:三元唯一、读与订阅分权、默认拒绝、撤销不删行)';
|
||||
|
||||
-- 自验(执行后逐条核对):
|
||||
-- SHOW CREATE TABLE form_ownership\G -- 应含 UNIQUE KEY `uk_ownership_form` (`form_uuid`)
|
||||
-- SHOW CREATE TABLE form_consumer_grant\G -- 应含 UNIQUE KEY `uk_grant` (三元键) 与 idx_grant_consumer
|
||||
-- SELECT COUNT(*) FROM information_schema.columns
|
||||
-- WHERE table_schema=DATABASE() AND table_name IN ('form_ownership','form_consumer_grant')
|
||||
-- AND column_name REGEXP 'secret|app_code|role|perm'; -- 必须 0 行(密钥与 app_code 都不进授权表)
|
||||
-- SELECT COUNT(*) FROM information_schema.referential_constraints
|
||||
-- WHERE table_name IN ('form_ownership','form_consumer_grant'); -- 必须 0 行(无物理外键)
|
||||
Reference in New Issue
Block a user