-- ============================================================ -- 表单中心 T13(契约 v2 §17.3)· 权限简化与匿名面 DDL(MySQL 5.7 兼容复核版) -- 范围:《契约v2-权限简化与开放接入.md》§17.3 三笔 = -- ① form.audience TINYINT→VARCHAR(64) + 存量迁移(1→'STUDENT'、2→'TEACHER',C1) -- ② form 加 creator_id / frozen / anon_cookie_minutes(C16、§4.2、C7) -- ③ answer 加 anon_ip / anon_ua + 复合索引 idx_answer_form_utime(C8/C12、§6.1) -- 批次硬前置:**T13 部署前必须执行**(audience 列型改后新代码按 VARCHAR 码串读写, -- ddl-auto=update 不会改已存在列的类型——本地/160 都必须手工跑本脚本)。 -- -- ⚠ 本脚本【不是幂等的】(沿 t10 先例:MySQL 5.7 无 ADD COLUMN IF NOT EXISTS / 无 MODIFY 判存在): -- 执行前逐条先查—— -- SHOW COLUMNS FROM `form` LIKE 'audience'; -- Type 应为 tinyint;已是 varchar(64) 则跳过 ① -- SHOW COLUMNS FROM `form` LIKE 'creator_id'; -- 有结果则跳过 ② 的第一条 -- SHOW COLUMNS FROM `form` LIKE 'frozen'; -- 有结果则跳过 ② 的第二条 -- SHOW COLUMNS FROM `form` LIKE 'anon_cookie_minutes'; -- 有结果则跳过 ② 的第三条 -- SHOW COLUMNS FROM `answer` LIKE 'anon_ip'; -- 有结果则跳过 ③ 的加列 -- SHOW INDEX FROM `answer` WHERE Key_name='idx_answer_form_utime'; -- 有结果则跳过 ③ 的加索引 -- 备份:`mysqld --single-transaction` 前先 mysqldump 本库(本地 bsch_formcenter / 160 容器网同名库)。 -- -- 5.7 复核要点(沿 t10):①无 CTE/WITH、无函数索引、无生成列、无 CHECK 约束;②utf8mb4 显式 CHARSET/COLLATE; -- ③InnoDB、无物理外键(creator_id→UserBase 用户主键、form_uuid→form.uuid 都是逻辑外键)。 -- -- 存量与语义: -- · audience 迁移后**不允许出现逗号串之外的形态**;空串/NULL 视为脏数据,读侧按默认 STUDENT 解释(C1)。 -- · creator_id 存量=NULL:解析期按 `create_by`(账号名)兜底判定创建人(C16), -- 一次性回填对照 UserBase 由人工/脚本另做(160 现存 form 行个位数);未回填行由兜底覆盖,不阻塞发布。 -- · frozen/anon_cookie_minutes 有默认值(0 / 10),存量行即"未冻结、默认 10 分钟",无需回填。 -- · anon_ip/anon_ua 只由 T15 匿名面写入,列先建不启用=部署即安全(§17.3 注)。 -- ============================================================ -- ① 面向人群改多值码串(C1:逗号分隔枚举码闭集 STUDENT/TEACHER/ANONYMOUS;禁止布尔补丁 D8) -- 顺序刻意:先改列型(tinyint 值 1/2 转成字符串 '1'/'2'),再把数字串映射成枚举码。 ALTER TABLE `form` MODIFY COLUMN `audience` VARCHAR(64) NOT NULL DEFAULT 'STUDENT' COMMENT '面向人群(C1 多选,逗号分隔枚举码:STUDENT/TEACHER/ANONYMOUS;原 TINYINT 1/2 由本脚本迁移)'; UPDATE `form` SET `audience` = 'STUDENT' WHERE `audience` = '1'; UPDATE `form` SET `audience` = 'TEACHER' WHERE `audience` = '2'; -- ②-a 创建人归一列(C16:现网 create_by=账号名字符串,与两表 user_id 标识体系不同,建列归一) ALTER TABLE `form` ADD COLUMN `creator_id` BIGINT NULL DEFAULT NULL COMMENT '创建人 user_id(BoaUser.id,新建时从会话落列;NULL=存量未回填,解析期按 create_by 账号名兜底)' AFTER `name`; -- ②-b 冻结开关(§4.2/D11:1=提交后禁编辑,双写通道同关;修正路径=撤回+重交) ALTER TABLE `form` ADD COLUMN `frozen` TINYINT NOT NULL DEFAULT 0 COMMENT '提交后禁编辑(1=开启,喂外部业务应用的表单建议开启;§4.2)' AFTER `answer_no_enabled`; -- ②-c 匿名防重窗口(C7:cookie 有效期分钟数,表单级可配,默认 10) ALTER TABLE `form` ADD COLUMN `anon_cookie_minutes` INT NOT NULL DEFAULT 10 COMMENT '匿名 cookie 防重窗口分钟数(C7,默认 10;仅 audience 含 ANONYMOUS 的表单有意义)' AFTER `frozen`; -- ③ 答卷匿名留痕两列 + 非冻结增量拉取索引(§6.1:现状 answer 表无任何 update_time 索引,已核实) ALTER TABLE `answer` ADD COLUMN `anon_ip` VARCHAR(64) NULL DEFAULT NULL COMMENT '匿名提交来源 IP(C8/C12,仅匿名答卷填充,登录答卷恒 NULL)' AFTER `idempotency_key`, ADD COLUMN `anon_ua` VARCHAR(512) NULL DEFAULT NULL COMMENT '匿名提交 User-Agent(C8/C12,只进独立列,不进 values_json)' AFTER `anon_ip`; ALTER TABLE `answer` ADD KEY `idx_answer_form_utime` (`form_uuid`, `update_time`, `id`); -- 自验(执行后逐条核对,V66/V67/V70 的库侧半边): -- SHOW CREATE TABLE form\G -- audience varchar(64) NOT NULL DEFAULT 'STUDENT' + creator_id/frozen/anon_cookie_minutes -- SHOW CREATE TABLE answer\G -- anon_ip/anon_ua + KEY idx_answer_form_utime(form_uuid,update_time,id) -- SELECT audience, COUNT(*) FROM form GROUP BY audience; -- 只应出现 STUDENT/TEACHER/组合码串,不得再有 '1'/'2' -- EXPLAIN SELECT id FROM answer WHERE form_uuid='<32位>' ORDER BY update_time, id; -- 应命中 idx_answer_form_utime