Files
form-center-service/sql/t3_answer_domain.sql
T
chenxing 49b095eb81
build / build-push (push) Successful in 1m18s
feat(read): T4 后端·检索统计+数据范围+导出
四条读出路径(列表/详情/统计/导出)与管理动作行级把关共用 AnswerScopeQueryBuilder
一个谓词构造器(契约 §8 红线);数据范围按 SysRoleEnum 矩阵×audience 解析 +
customId=fc:scope:all 扫描授权模块树,多来源取并集、全不命中回落仅本人提交。
四铁律落地:两段式列表禁 SELECT values_json、keyset 仅提交时间序(游标对外是 uuid)、
覆盖索引化(新增 idx_answer_form_time)、统计缓存键含 form_uuid+version 且仅升版失效+短 TTL。

T3 验收裁决前置三项:P25 answer.form_uuid 精确钉版溯源(删平台级回退启发式)、
P33 代填 subject 存在性+同校(SubjectDirectory 策略接口,生产走 /oapi/user/byId)、
P34 附件 key 与 store_key 一致性比对;P21 FormAdminGuard 整体替换为"授权模块集"口径
(ModuleTreeSource 策略接口,与 scope 解析共用同一棵树)。

新增对外读接口 GET /oapi/form/schema(内联 options+valueSchema)与 /oapi/form/answer
(钉住版本 schema+值文档);导出 easyexcel 流式同步响应,选项列显 label、附件列显 fileName。

自验:mvn -B -o clean package 177 条全绿(本轮新增 56,23 条打真实 MySQL),
覆盖 V9/V11/V23/V27/V28/V37/V42 + V30 读侧半边 + V39 范围半边;
V9 以"发布时补建索引行"实现(方案未指定机制,记 P36)。详见 docs/T4-report.md(问题单 P35~P46)。
2026-10-14 19:00:15 +08:00

127 lines
9.0 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.
-- ============================================================
-- 表单中心 T3 · 答卷域 DDL(MySQL 5.7 兼容复核版)
-- 范围:契约 §10 九表中的答卷域五表 = answer / answer_index / status_log / answer_op_log / fill_quota
-- 另加 form_file(中心上传附件落档行;契约 §10 未列,但 §3 值形状含 id → 见 T3 报告问题单 P24)
-- 用途:评审权威 + 生产 ddl-auto=none 时的升级脚本底稿;开发期仍由 ddl-auto=update 建表。
-- 5.7 复核要点:①无 CTE/WITH、无函数索引、无生成列;②utf8mb4(本脚本显式写 CHARSET/COLLATE);
-- ③InnoDB、无物理外键(方案 §7.7 逻辑外键约定);④TEXT/MEDIUMTEXT/LONGTEXT 一律不写 DEFAULT(5.7 不允许);
-- ⑤索引键长:单列 uk/普通列的 utf8mb4 上限 767 字节=191 字符(v_str、store_key 均按此定宽),
-- answer_index 三条复合索引键长约 1292 字节,依赖 5.7 默认 ROW_FORMAT=DYNAMIC + innodb_large_prefix=ON
-- (上限 3072)——160 生产 MySQL 版本与行格式待核(方案 §7.7/P10 记档项),
-- 若为 COMPACT 或 large_prefix=OFF,需把 v_str 降到 varchar(64)(前缀匹配语义不变,可筛选更短)。
-- P5/P15 裁决:实体继承 BaseEntity,实表=契约草案 + create_by/update_by 两列;
-- 审计列在基类未标 nullable=false,故按建表实态写 DEFAULT NULL(Listener 恒写 now(),业务上不为空)。
-- 本机取证:MySQL 8.0.45 / bsch_formcenter 由 ddl-auto=update 建表后 SHOW CREATE TABLE 逐列比对一致。
-- T4 变更(本轮):①answer 增列 `form_uuid VARCHAR(32) NOT NULL`(P25 裁决落地,钉版溯源精确坐标);
-- ②answer 增索引 `idx_answer_form_time`(方案 §5.5 铁律③,四读出路径 keyset 游标的覆盖索引)。
-- 开发库 answer 当时零行 → NOT NULL 无需回填;已建库由 ddl-auto=update 追加列与索引,
-- 生产(ddl-auto=none)升级即照本脚本这两条 ALTER/CREATE INDEX 执行。
-- ============================================================
CREATE TABLE `answer` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`uuid` VARCHAR(32) NOT NULL COMMENT '数据面运行时身份(详情 URL/嵌入地址/L2 引用,契约 §1)',
`form_uuid` VARCHAR(32) NOT NULL COMMENT '提交时解析命中的表单 uuid——钉版溯源的精确坐标(P25 裁决:遮蔽歧义根治,读改路径一律按它取版本行)',
`sch_id` BIGINT NOT NULL COMMENT '恒=会话校(校域不变式:跨校提交在解析层即不可能)',
`app_code` VARCHAR(64) NOT NULL,
`code` VARCHAR(64) NOT NULL,
`version` INT NOT NULL COMMENT '钉住的 schema 版本(提交时解析到的发布版,升版不回改)',
`submitter_id` INT NOT NULL COMMENT '谁提交的(CAS 会话推导,不可外部传入)',
`subject_id` INT DEFAULT NULL COMMENT '关于谁(代填时=被代填人;普通用户只能=自己)',
`status` VARCHAR(64) NOT NULL COMMENT '轻状态位 code,取值域=form.status_defs(一等列,方案 §5.3②)',
`values_json` MEDIUMTEXT NOT NULL COMMENT '值文档 {key:value}(TEXT 的 64KB 会被长文本答卷顶爆)',
`answer_no` VARCHAR(64) DEFAULT NULL COMMENT '报名号 {code}-{yyyyMMdd}-{当日序号},表单级开关默认关',
`idempotency_key` VARCHAR(64) NOT NULL COMMENT '客户端幂等键;库级唯一——防双击只有 DB 唯一索引靠得住',
`is_deleted` TINYINT NOT NULL DEFAULT 0 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_answer_uuid` (`uuid`),
UNIQUE KEY `uk_answer_idem` (`idempotency_key`),
KEY `idx_answer_form` (`sch_id`, `code`, `status`, `id`),
KEY `idx_answer_submitter` (`sch_id`, `submitter_id`),
KEY `idx_answer_subject` (`subject_id`),
KEY `idx_answer_form_time` (`sch_id`, `app_code`, `code`, `is_deleted`, `id`) COMMENT 'T4 铁律③:单表单列表/导出/计数 keyset 游标的覆盖索引'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='答卷(方案 §5.1:定义与答卷物理分离)';
CREATE TABLE `answer_index` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`answer_id` BIGINT NOT NULL COMMENT '逻辑外键 → answer.id',
`sch_id` BIGINT NOT NULL,
`code` VARCHAR(64) NOT NULL,
`field_key` VARCHAR(64) NOT NULL COMMENT '只写显式声明 searchable 的字段(V3/V9)',
`v_str` VARCHAR(191) DEFAULT NULL COMMENT '文本/选项值;utf8mb4 下 767 字节索引上限,超长截断=前缀匹配',
`v_num` DECIMAL(20,6) DEFAULT NULL COMMENT '数值值(precision 白名单上界 6 与此同口径)',
`v_date` DATE DEFAULT NULL COMMENT '日期值(禁 TEXT 字典序比日期,方案 §4.3④/V4)',
`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`),
KEY `idx_ai_str` (`sch_id`, `code`, `field_key`, `v_str`, `answer_id`),
KEY `idx_ai_num` (`sch_id`, `code`, `field_key`, `v_num`, `answer_id`),
KEY `idx_ai_date` (`sch_id`, `code`, `field_key`, `v_date`, `answer_id`),
KEY `idx_ai_answer` (`answer_id`) COMMENT '软删同步删行用'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
COMMENT='索引值表:一 searchable 字段值一行(多值多行),同时支撑筛选与分组统计';
CREATE TABLE `status_log` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`answer_id` BIGINT NOT NULL,
`status_code` VARCHAR(64) NOT NULL,
`operator_id` INT NOT NULL COMMENT '操作人;填表人改状态在接口层即被拒(V14)',
`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`),
KEY `idx_sl_answer` (`answer_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
COMMENT='状态留痕:只记"谁把这条标成什么",系统内不存在任何转移规则校验(V15)';
CREATE TABLE `answer_op_log` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`answer_id` BIGINT NOT NULL,
`op` VARCHAR(32) NOT NULL COMMENT 'edit/soft_delete/change_status/batch_soft_delete/batch_change_status',
`operator_id` INT NOT NULL,
`summary` 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`),
KEY `idx_ol_answer` (`answer_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='操作留痕(方案 §5.4)';
CREATE TABLE `fill_quota` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`form_id` BIGINT NOT NULL COMMENT '限次是表单级(跨版本共享名额)',
`limit_key` VARCHAR(128) NOT NULL COMMENT 'coalesce(subject_id, submitter_id) 字符串化',
`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_quota` (`form_id`, `limit_key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
COMMENT='限填登记:提交事务内插入、撞键即拒(并发兜底);软删答卷同步删行=释放名额';
CREATE TABLE `form_file` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`sch_id` BIGINT NOT NULL,
`file_name` VARCHAR(255) NOT NULL COMMENT '用户上传时的展示名(已剥路径分隔符与上跳)',
`store_key` VARCHAR(191) NOT NULL COMMENT '相对存储 key=form/yyyy/MM/{32hex}.{ext};不可猜测串即访问控制(方案 §7.7)。191 而非 255:本列有唯一键,utf8mb4 下单列索引 767 字节上限',
`file_size` BIGINT NOT NULL COMMENT '字节数(≤50MB 全局兜底在上传时判)',
`ext` VARCHAR(16) 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_form_file_key` (`store_key`),
KEY `idx_form_file_sch` (`sch_id`, `id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
COMMENT='中心上传附件落档(值形状 {id,fileName,key} 里的 id 由本表提供;绝不存绝对 URL)';