38 Commits
Author SHA1 Message Date
chenxing 1717f4c47e Run mandatory RC gates with isolated CI failure checks
build / build-push (push) Successful in 3m13s
Local review candidate only. Explicitly selected inherited prerequisites and prior repair patches; unrelated WIP and independent MQ fixes are excluded. No push or deployment authorized.
2026-10-23 10:51:19 +08:00
chenxing 6ff39b0cec Define authoritative reconciliation acknowledgements
Local review candidate only. Explicitly selected inherited prerequisites and prior repair patches; unrelated WIP and independent MQ fixes are excluded. No push or deployment authorized.
2026-10-23 10:51:16 +08:00
chenxing c6e061815a Enforce exact published form version contracts
Local review candidate only. Explicitly selected inherited prerequisites and prior repair patches; unrelated WIP and independent MQ fixes are excluded. No push or deployment authorized.
2026-10-23 10:51:13 +08:00
chenxing e39164a2bd Preserve reviewed existing gate prerequisites
Local review candidate only. Explicitly selected inherited prerequisites and prior repair patches; unrelated WIP and independent MQ fixes are excluded. No push or deployment authorized.
2026-10-23 10:51:10 +08:00
chenxing 256735fb43 feat(openapi): 对账派发改 by-key 注册表寻址(appCode 白名单+fail-closed),显式 targets 例外保留
build / build-push (push) Successful in 1m27s
2026-10-22 17:14:22 +08:00
chenxing a00f97f0ec fix: answerReconcileProperties @Bean 拆到无依赖独立配置类——Boot 2.6+ 默认禁止一切循环引用,字段注入不豁免(160 复验仍拒启)
build / build-push (push) Successful in 50s
已用 MqDisabledContextIT(@SpringBootTest 全上下文)验证装配通过:Tomcat 起来+健康检查 UP。
2026-10-22 10:44:04 +08:00
chenxing 885b47226f fix: 对账配置类改字段注入,解开 Configuration→Service→Properties 构造环(160 部署实测启动失败)
build / build-push (push) Successful in 1m15s
2026-10-22 10:33:46 +08:00
chenxing d7473112b0 t1~t3落地+10-22精修: 消费授权v3(owner/grant/订阅/目录)+答卷投递v2(可信提交上下文/快照/幂等关联)+答卷对账v1(清单/派发/租约/结果)
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编译)。
2026-10-22 10:02:57 +08:00
ops 4b76d616dd fix(ci): 构建容器名唯一化(避免并发构建互相 rm -f 致 exit 137) 2026-10-21 02:26:47 +00:00
ops f8a7bdd4e4 ci: apk 源统一走 aliyun(规避 dl-cdn 时段性被拦卡 60s) 2026-10-21 01:36:58 +00:00
chenxing b2437a2074 S8收口:audience数组漂移闸断言入册(byUuid/byCode两寻址同集合)
build / build-push (push) Failing after 2m8s
2026-10-20 22:37:39 +08:00
chenxing be2752ed4d t18:批量导入——逐行走 submit 管线 + C13 确定性幂等键 + C14 表头先校验与逐行结果
- 新 AnswerImportService:模板(两行表头:当前发布版字段 label + 固定第一列「被记录人ID」,第二行说明)
  与导入两端点都挂在 /api/form/admin/data 下、守卫与导出同一把 requireData(该表单 ALL 档)。
- 每一行拼成一次普通提交请求进 AnswerService.submit(V60 采集管线唯一):值校验、按钉住版本解释、
  限次、截止、幂等、索引、MQ 事件与手工填报逐字同一套;导入侧不写第二份校验,只做
  "读 xlsx → 按 label 映射列 → 最小类型转换(number/多选数组,其余留字符串)→ 逐行收集结果"。
- 坏行不静默:逐行 {rowNo, ok, answerId|error},失败原因是管线原话不改写;rowNo 用文件物理行号,
  "第 7 行"能直接指到那一格。附件类字段逐行点名"批量导入不支持上传",不静默丢列。
- 整批在写库之前拒的三件(C14):重名 label(模板生成侧同样先判,不给带歧义的模板)、
  表头有当前发布版不存在的列、行数超 1000(超限整批拒不是截断)。
- 幂等键 C13 定稿:sha256("import:" + 表单 uuid + "|" + subject + "|" + 规范化值文档) 前 40 位。
  批次串取表单坐标——请求级随机串等于没有幂等,文件字节摘要会让"改一个字重传整份"把未改的行
  全部重插(1000 行修 1 行 = 999 条重复);也不含行号。前缀让导入键与手工填报的客户端 uuid 键不相交。
  登记问题单 P128(已按本单回写契约 §5.1/C13)。
- FormAnswerReadController 加 GET /import-template(xlsx 流,原生 fetch 侧配套)与 POST /import(JSON 信封)。

测试 AnswerImportIT(V60/V74)12 例:模板形状与说明行、未发布两侧拒、导入行带 form_uuid+version 且
searchable 字段照写 answer_index、四类坏行逐行原话 + 物理行号、被记录人非整数逐行拒、
代录行 subject/submitter 归属正确且非 ALL 档进不到服务、同一文件重传逐行原 answerId 且改一格只新增那一行、
限次照挡、三类整批拒且零写入。门禁定向 12 例全绿(s7/import-it-4.log,MVN_EXIT=0)。
2026-10-20 17:40:16 +08:00
chenxing b3478afddd t17:oapi v2 增量拉取 + RabbitMQ 事件(可选依赖、信封十字段闭集、软删防回归断言)
- GET /oapi/form/answers(契约 §6.1/C9/C11/C12):域校验走 readDomainForm(他校与不存在同一条
  "表单不存在",V54;软删表单照读,§5.4/P32),行集恒带 sch_id。增量口径按冻结状态二分且不接受混用
  (混用给可诊断拒绝,而不是静默返回一个"看起来正常"的空窗口):
  冻结=append-only + after=answer_uuid(服务端按 uk 换算库内 id,id 绝不出响应,V53,墓碑照常下发);
  非冻结=updated_since + 复合键 (update_time, answer_uuid),续拉游标 {ISO8601}|{answer_uuid}(P126)。
  limit 1~2000 越界拒不夹;nextCursor 只在这一页填满时给(宁可末尾多要一次空页,不可少要一次)。
- 行字段是 §6.1 的 15 键闭集(逐字段 @JsonProperty,外层 rows/nextCursor 按 C9 原文 camelCase——
  命名差异是契约原文,不加"顺手统一"的适配层);meta.ip/ua 只匿名行有值(C12),登录行两项 null。
- 时间线格式降到整秒并对齐列精度(DATETIME 秒粒度;带纳秒的游标与列值比不出等值,
  同事务重读还会经一级缓存拿到内存对象):取舍方向是"宁可重复投递也不漏行",
  重复投递正是 C9 明写的 at-least-once(P127)。IT 侧 flush+clear 把读取摆回生产形态。
- 新 FcEventPublisher(§6.2/V62/V63/V64):RABBITMQ_HOST 未注入=整体 no-op + 启动一条说明日志;
  RabbitTemplate 走 ObjectProvider(可选依赖不该在缺 bean 时拖垮上下文);exchange fc.events.v1
  惰性 durable topic 声明(声明失败放回标志位允许重试);routing key fc.answer.{app_code}.{action};
  afterCommit 发布且信封在注册前按实体快照组好;发布失败只 warn 不抛(没有 outbox、没有死信)。
  信封十字段闭集、不含值文档(裁决 1:MQ 是通知通道不是数据通道)。
- 发布点三处:submit 写库段后=created(代录/导入同管线自动获得);两处编辑写库段=updated
  (能走到即说明未开冻结,V61 已在前面拦);两处软删实体 save 后=tombstoned(公共出口是发布器那一个方法)。
- 依赖与配套:pom 加 spring-boot-starter-amqp(BOM 管版本);yml 的 spring.rabbitmq.* 全从
  RABBITMQ_* env 现读,并关 rabbit 健康探针与 Boot 的 RabbitAdmin(dynamic=false)——
  后者启动就拨号声明拓扑,无 broker 环境实测把上下文启动拖到分钟级,拓扑已由发布器自己声明。

测试:OapiAnswersIT 8 例(闭集无 id、口径二分互不混用、limit 边界、after 翻页+重放+墓碑可见、
V70 软删前移 update_time 才让墓碑进窗口、复合游标带 uuid 尾、他校同文面、匿名行 meta)
+ FcEventPublisherTest 5 例(V63 十字段闭集、routing key、未配置即 no-op、afterCommit 之前不发、
异常被吞)+ MqDisabledContextIT 2 例(无 broker:enabled()=false 与 /actuator/health 仍 UP)。
门禁:定向 15 例全绿(s6-gate-2.log),本段另跑过一次全量 288 例全绿(mvn-full-2.log,
按协调口径后续段不再连发全量,全量留 S8 收口)。差额 P126/P127/P128。
2026-10-20 17:17:40 +08:00
chenxing c41ffebb95 t16:app 注册表——四业务字段零角色 + C17 CRUD 端点族 + 在用禁删 + 入口链接生成
- sql/t16_app_registry.sql:§17.2 建表(uk_app_code 全库唯一、form.app_code 逻辑引用不建 FK)。
  本机核对:dev 库 ddl-auto 建的表与脚本列型逐列一致,且 Hibernate 尊重显式索引名(无漂移);
  脚本另在临时库实跑一遍校验语法后已清理。
- 实体 AppRegistry:app_code/sch_id 均 updatable=false——code 是入口链接与 routing key 的组成段,发布即冻结。
- AppRegistryService 四条写侧规则:C10 格式 + 保留字 form-center(大小写变体也算撞名);
  撞 uk 前先按 code 点名(在用=已存在 / 只剩软删行=编码不复用,都是可诊断 CUSTOM(5) 而不是库内 500,P125);
  删除保护按 countLiveByAppCode 全库统计(code 全库唯一,他校还在用同样算在用,取窄的一边);
  删除=实体 save 改标记(软删全仓保持实体写,bulk UPDATE 会绕过审计监听器)。
- 域规则:清单=本校∪平台级;改/删只动 sch_id==本次域的行,他校行按"app 不存在"回答。
  超管建平台级需显式 schId=0,与两表端点同一条代查口径,零新增判据。守卫复用 requirePlatformAdmin(C4③)。
- 入口链接由后端生成:{context-path}/front/#/app/{code}/,本地无 context 段时回落 /#/app/{code}/
  (同 host.config.ts 的推导),前端只补 origin。
- 新增 GET/POST/PUT/DELETE /api/form/admin/apps。

测试 AppRegistryIT(V59/V65/V69,8 例):反射断言持久字段恰好 appCode/name/schId/isDeleted 且无
role|perm|admin|user|auth 形态列名;保留字与 6 种非法 slug + 2/64 位边界;重复注册不撞库;
本校∪平台级清单与链接后缀;普通教师 FORBIDDEN、他校"app 不存在";改名只改 name;
草稿态即算在用(msg 带张数),表单软删后才让删;软删后 code 不复用。
门禁 mvn -B -o clean test → Tests run: 273, Failures: 0, Errors: 0。
2026-10-20 14:30:10 +08:00
chenxing 3dac43dcfc t15:匿名通道落地——/anon/form 三端点 + HMAC cookie 防重 + 提交管线分通道
- 新 controller/anon/AnonFormController(RequestMapping /anon/form,不带 /api:CAS starter
  的 6 个 Filter 只挂 /api/* 且无豁免位,A11):resolve/submit/upload 三端点,签名里不出现 BoaUser。
- FormDefService 抽出 publishedOf 共用生命周期判定,新增 resolvePublishedForAnon/resolveForAnon:
  全域取"已发布未删 + audience 含 ANONYMOUS",非匿名/草稿/停用/不存在/跨校歧义同一句"表单不存在"
  (C6/V54 防探测;歧义 fail-closed 见 P119);出参复用登录面同一个组装实现。
- AnswerService 提交身份收成 SubmitIdentity(ofSession/ofAnon 两个工厂,通道由入口显式声明而不是
  从 user==null 反推):匿名面 sch_id/submitter_sch_id=表单归属校、submitter_id=0、subject_id 留 NULL、
  不进人群门也不占 fill_quota,值校验/附件/钉版/报名号/索引一段代码共用(V56/V57/V60)。
- 新 AnonReplayGuard:cookie 值=服务端 HMAC 时间戳(密钥复用 yh.app-auth.app-secret,零新增配置项、
  零服务端会话状态),窗口按 form.anon_cookie_minutes;伪造/篡改/跨表单按"没带 cookie"放行,
  命中=CUSTOM(5) 文案含剩余分钟;只在真提交成功时签发,幂等重放优先于节流且不续窗口(C7)。
- AnonModels 分 DTO:匿名请求体结构性没有 subjectId;Context 只从 HTTP 请求取(IP/UA/节流剩余秒)。
- FormFileService 加 uploadForTenant(schId, file)(匿名上传落表单归属校,否则提交侧附件归属查不到);
  五条硬约束一条不分叉。留痕按列宽截断落 answer.anon_ip/anon_ua(列在 t12 DDL 已预建,本批启用)。

测试:新增 AnonFillIT(V67,8 例:防探测文案等值、落库形态、平台级租户 0、窗口内二发 CUSTOM(5)、
伪造无效、幂等命中优先、窗口按表单配置、上传落档校)+ AnonReplayGuardTest(7 例:窗口边界与密钥缺失
fail-closed)。门禁 mvn -B -o clean test → Tests run: 265, Failures: 0, Errors: 0。
落地差额登记 P119/P121/P122/P123(详见 docs/T12-T18-report.md §1)。
2026-10-20 14:04:42 +08:00
chenxing e4bf960e1b t14:模块行退役 + 菜单自解析——守卫换 C4 三段角色门,新增 managed/capabilities 端点
- FormAdminGuard 整体重写:requireCreate(超管∪校管理员,G1)/requireDefinition(+该表单创建人)
  /requireData(该表单 ALL 档)/requirePlatformAdmin(校管理员∪超管);答卷寻址的入口
  先由 uuid 反查所属表单再判同一道门(requireDataByAnswer/requireDataByAnswers);
  租户域推导(超管可指定含 0、校级只能本校、传 0 即拒)一字未改
- 摘除 ModuleTreeSource/PlatformModuleTreeSource/FormModuleKeys 与 GetUserMod 端点
  (§19 作废-10:中心不再消费授权树,B14 缺口与 H4 依赖随之消失)
- 新增 GET /api/form/my/managed(C3:创建人 ∪ form_admin,role 标来源)与
  GET /api/form/my/capabilities({schoolAdmin},"系统设置"入口唯一数据源);
  managed 行额外带 formUuid/status(P120:只给 C3 的"fillable 行+role"点不进设计器/数据页)
- 两表/app 注册/GetSchoolAll 守卫切到 requirePlatformAdmin;字典同样吃这一道门
- h2_userbase_app_and_modules.sql 文件头加"整份作废、不再在任何环境执行"注记(正文保留供追溯)
- 测试:FormAdminGuardTest 按三段门重写(含批量入口跨表逐张判 ALL、fail-closed 与域推导);
  FormBaseControllerTest 改成"GetUserMod 端点反射级不存在"断言;AdminDomainIT/MyAnswerIT/
  AnswerAdminIT/AnswerReadIT 的模块行断言换角色门载体;PerfTreeCallsProbe(P44 取树次数)
  随度量对象消失而删除
2026-10-20 13:20:58 +08:00
chenxing f10b367bbc t13:所有权模型切换——audience 多值门 + ALL/SELF 两档 + 冻结开关,三层模型与身份矩阵出清
- DDL sql/t12_access_simplification.sql(非幂等,先查列):form.audience TINYINT→VARCHAR(64)
  + 存量迁移、form 加 creator_id/frozen/anon_cookie_minutes、answer 加 anon_ip/anon_ua
  + idx_answer_form_utime;实体与 @Index 同步(本地库已备份并手工执行)
- audience 多值(C1/C2/C5):FormEnums.Audience 扩 ANONYMOUS + parse/join/namesOf 单点工具;
  配置通道收数组并加跨字段约束(含 ANONYMOUS 时 fill_limit 必 0,CUSTOM(5));
  FormView/ResolvedFormView/FillableRow/oapi SchemaView 出参一律数组(flow-center 需通告)
- 两档范围(§2.3/D4):DataScopeService 重写为 {all,self} + 平台级表单的租户限定变体(D9),
  身份默认矩阵与回落分支删除;5 实现器收敛为 ALL/SELF 两个,SCHOOL 谓词并入 ALL 变体、
  TEACHING_CLASS 摘除;AnswerScopeQueryBuilder 改吃查询规格(V55 唯一出口不变)
- audience 门替第 0 关(§3.1):AnswerService 的 fillAccess.resolve+requireFillEntry 换成
  人群门,requireSubject 指定权收敛为"该表单 ALL 档";FillAccessService 全类删除;
  代录/导入不过人群门(P116 用户裁决:§3.4 与 §5.1/V73 冲突取"功能可用"一侧)
- fillable 行形状(C2):删 canFill/myTier,入集=身份类型 ∈ audience(与提交同一公式)
- 冻结开关(§4.2/V61):config 通道 + 两条编辑通道同关,文案指路"撤回后重新提交";
  新增 AnswerFrozenIT 钉双通道与修正路径
- 创建人识别(C16):creator_id 建列 + create_by 账号名兜底(兜底取会话线程账号,
  与 create_by 同一事实源,不拿 BoaUser.account 跨系统比串)
- 测试:FillAccessServiceTest 整类删除(V48 作废)、AnswerFillGateIT 按 v2 语义重写(V66)、
  DataScopeServiceTest 按两档重写、AdminScopeIT/AnswerExportIT/ReadFixture 造数改自填路径
2026-10-20 12:54:07 +08:00
chenxing fa4318cfc2 t12:FormAccessPolicy 门面收敛——会话推导六处+角色常量三处收编,零语义变化
会话校推导/身份角色并集/超管判定收进门面单点,角色常量与校领导临时清单 {2,7}
同址(V58 单点);判定逻辑与拒绝文案逐点沿用原实现,既有 272 例测试一字未改全绿。
清单外补件:FormFileService 的 requireSessionSchId 是 B3 未列的第七处,一并收编。
2026-10-20 12:00:46 +08:00
chenxing 13abc2dbc7 t11-schema: /oapi/form/schema 支持按 form_uuid 查询(uuid 优先 + version 精确取行)
build / build-push (push) Failing after 2m2s
flow-center 在流程定义发布期拉契约快照的数据源(v2 方案 §4.4),code 查询路径逐字节不变:

- Controller:新增可选 uuid(32 位串)/version(Integer),code 由必填放宽为可选;uuid 与
  code 同传时 uuid 优先(类头 javadoc 写明形态与用途);响应形状零改动,仍是同一个 SchemaView。
- FormDefService.resolvePublishedByUuid(纯新增):域校验复用 readDomainForm(本校 ∪ 平台级,
  他校与不存在的 uuid 同一回答,不给探测面);不给 version=入口口径——要求 PUBLISHED 且
  currentVersion 非空,文案与错误码族对齐 resolvePublished,并额外挡软删行(code 形态取域
  SQL 带 is_deleted=0,不挡就是同一张表两份可读集);给 version=溯源口径——pinnedForData
  同款,不吃生命周期、不过滤软删,缺行给 PARAM「版本行不存在:<appCode>/<code> v<n>」。
- OapiFormService:五参重载只做入口分流,原三参方法体一字未动(numstat 删除行=0);只给
  version 不给 uuid 显式拒(静默忽略会让调用方误以为拿到的是那个版本)。SchemaView 组装段
  按任务书 W2-1「原方法体不重构不挪动」保留两份,靠 F1 的 groups 逐字比对作漂移闸。

问题单 P106~P111 已回填执行手册 §5;《契约》§9 增补两形态并新开 §9.1(参数表/示例/错误码/
「uuid 形态不经遮蔽」与「两条分支口径不同源」两段说明)。

验证:mvn -B -o test 全量 272 例绿(0 failures / 0 errors,BUILD SUCCESS)。
新增 11 例=OapiFormServiceIT F1~F7(uuid 已发布版/精确历史版/平台级/防枚举同文案/
未发布·停用·软删/版本行不存在/uuid 优先)+ OapiFormControllerTest 4 例(不传 code 不 400、
version 绑成 Integer 且缺省 null、五参原样透传)。基线 261 → 只增不减;
F8 回归=既有四个 code 形态用例一字未改(该测试文件 diff 为 +144/-0)。
2026-10-19 19:56:46 +08:00
chenxing 7af6a98fc0 fix(post-p95): 跨版本编辑必挂/编辑不吃截止闸/自填 subject 归一/报名号并发重号
P95 落地后的独立修复批,四条存量逻辑硬伤(填报入口层不动):

- 跨版本编辑必挂:客户端按当前发布版渲染、服务端按钉住旧版校验,升版后编辑永远
  400(含未知字段/缺必填)。改为编辑态吃 /my/detail 的钉住版 groups。
- 编辑不吃截止/停用闸:先占位、截止后随便改可绕过 deadline。editBySubmitter 补
  截止/停用/已删三闸,与 submit 同口径。
- 自填 subject 落 NULL:subject_id 为空的自填行进不了"关于我的"(/my/about 与
  ABOUT_ME 谓词都按 subject 查),V18"含代填"实际退化成"仅代填"。落库归一为
  coalesce(subjectId, submitterId);限次键本就是 coalesce,不受影响。
- answer_no 并发重号:count+1 的 count 与 insert 之间有并发窗口,报名号是对外凭证。
  加 uk_answer_no 兜底 + 撞号重算序号重试(3 次封顶),按异常消息列名区分撞的是
  报名号还是幂等键(MySQL/H2 通用)。

语义变更备案:自填行 subject 由 NULL 变本人,读矩阵"学生x面向学生=关于我"这一格
此后含自填行,可见集变大(DataScopeService/矩阵规则一字未动,变的是数据形状)。
三处断言随行为更新:AdminScopeIT、AnswerReadIT、MyAnswerIT(+ReadFixture 注释)。

验证:mvn 261 绿(+2 新用例:并发报名号不重复、编辑闸三态)。
2026-10-16 15:04:20 +08:00
chenxing a02f47ad5c feat(p95-fill): 填报入口收回中心——三层角色模型 + 提交第 0 关 + 我可填清单
build / build-push (push) Successful in 58s
任务书 P95 W1/W2/W3/W7。发一张表单原本要 UserBase 超管加一条 /f/{code} 模块行并组授权,
实测成本不可接受(10 张表=10 次跨系统操作),人群判定收回表单中心自解析。

- FillAccessService:audience × 会话身份(roleIds ∪ currentRole)× 管理员两表 → 层集合
  ADMIN/MANAGE/FILL/空=NONE,谓词 canFill/canProxy/isAdmin + topTier;与 DataScopeService
  读矩阵、FormAdminGuard 管理端入口三条线正交,结论互不供给
- AnswerService.doSubmit:钉版解析之后插第 0 关(先鉴权后业务,幂等重落在闸门后);
  requireSubject 吸收行为表——教师×面向学生只能代填本校学生(P33 existsInSchool 保留),
  层③代填仍沿 canManage 现路径,层①普通成员代填照旧拒
- GET /api/form/my/fillable:会话校 ∪ 平台级取域、复用 FormDefService.pickByShadowing 做
  (app_code,code) 遮蔽去重、仅已发布未软删、NONE 不入集,行带 canFill + myTier 两件事,
  deadline 升序 null 在后;新增 MyModels.FillableRow
- h2 登记脚本快应用段 5→4 行:删 /f/ 填报行与学生组授权,保留应用行/管理页面行/
  path 空导入导出锚点/学校开通;H2 冻结延续,本轮未在任何环境执行
- 测试:FillAccessServiceTest 16(V48)+ AnswerFillGateIT 14(V49)+ FillableListIT 10(V50),
  mvn -B -o clean test 全量 259 绿(教师×面向学生的造数据通道按新口径改代填,
  读矩阵与限次等断言判据一字未改)

DataScopeService 与 FormAdminGuard 两个文件本轮未被改动(红线 1/2)。
2026-10-16 12:43:31 +08:00
chenxing dc76574118 feat(t10-admin): 管理员体系——校级管理员+单表单管理员+锚点退役
build / build-push (push) Successful in 2m12s
- 新增 school_admin/form_admin 两表(sql/t10_admin_domain.sql,含 answer.submitter_sch_id 加列)与 AdminDomainController/AdminDomainService/SchoolUserTreeService
- DataScopeService 范围体系按 P80 验收口径落地:入口只认模块行、查询域=会话校、两表只升范围;新增 SchoolScopeImplementor
- fc:scope:all / SCOPE_ALL_CUSTOM_ID / anchorCustomIds / MODULE_DICT 锚点全链路退役,全仓引用归零;h2 登记脚本 6→5 行(快应用段逐字未动)
- AnswerScopeQueryBuilder 校域不变式:answer.sch_id 恒=会话校,基础过滤 sch_id+app_code+code+is_deleted 不变
- FormAdminGuard:targetSchId=0(基础表单)仅超管可维护;FormAdminController/AnswerReadController 等按管理员范围收口
- 测试基线 190→219/0/0/0:DataScopeServiceTest 22、FormAdminGuardTest 14、AdminScopeIT 8、AdminDomainIT 10、AnswerReadIT 改写 1、FormBaseControllerTest 7 等
2026-10-15 20:25:03 +08:00
chenxing bf39c220f2 test(perf)+sql(h2): T9 压测探针与 UserBase 登记交接脚本
build / build-push (push) Successful in 1m31s
Perf200kProbe(20 万答卷/78 万索引行,独立库 bsch_formcenter_perf):
- V10 量化:列表首屏 p95=22ms、三条件筛选 p95=313ms、深分页 keyset p95=31ms、
  统计 p95=1~334ms、导出 2 万级 4.1~8.1s(冷缓冲池首跑 22.1s 已如实记档)
- 铁律取证:org.hibernate.SQL 日志抓真实语句——第一段只 select id、不含 values_json;
  翻页谓词是 answer.id<游标;EXPLAIN 显示首屏/计数走 idx_answer_form_time 且 using_index=true
- P26:8 线程×250 并发实测 answer_no 重号率=1392/2000(69.6%,"不限次+报名号"组合)
- P37:新答卷后 cached=true、升版后 cached=false(铁律④语义与实现一致)
- P35/P31:information_schema 复核索引基数与 ROW_FORMAT=Dynamic(本机 8.0.45)
- 类名不带 Test/IT → 不进 mvn test 门禁(190 用例仍是快门禁),手点 -Dtest 才跑

PerfTreeCallsProbe(控制器形状,复用同一份数据):
- P44 请求级取树次数:列表/详情/导出=2、统计=3、筛选候选=1(P44 的"1~2 次"上界在统计路径实测为 3)
- V10 请求级延迟:筛选 p95=163ms、导出 2297ms/1.15MB
- P36 升版补建实测:19,600 行表单的发布=373,272ms 且本次补出 0 行——
  P36 记档的"2 万级秒级"不成立,N+1 逐行探测是主因(数字交验收轮裁决是否触发 publish 处改造)

sql/h2_userbase_app_and_modules.sql(H2 + 第一个快应用登记交接件):
中心应用六条模块行(手册 §2.2,含 path 空 fc:scope:all 锚点)+ 快应用五条模块行
(方案 §6.2 checklist,导入/导出为 path 空锚点)+ 组/角色授权与开通 + 验收查询 + 回滚段。
AI 不代跑(沿 flow-center 先例;H2 既定路径是超管在管理端录入,脚本为批量/复核形态)。
步骤 3.5 显式记录"快应用数据视图的后端守卫仍扫中心树"这一过渡口径(T9 报告 P78 附注)。

docs/T6-report.md 已按 P69 从本仓 docs/ 挪至 form-center/docs/(本提交不含该文件)。
2026-10-15 12:57:32 +08:00
chenxing 12a8df4fef test(my): 我的填写六端点单测闭环 P70/P75(my 面实现随批入库)
MyAnswerIT 12 例(真库 @Transactional 回滚,沿 T3/T4 IT 惯例):
- 身份全会话推导:六端点首参恒为 BoaUser、入参无 id 型身份位、请求体只有
  answerId/values/summary;未登录一律 UNAUTHORIZED;跨校同 uuid 读不到(V17)
- 越权无探测面:他人答卷与"不存在"同码同形(PARAM_ERROR + 同一句话,仅 uuid 不同);
  非提交人改/删 FORBIDDEN;被代填人可读不可改(V29 的我的面形态)
- 软删释放名额:本人删自己的行 → isDeleted=1、索引行清空、名额释放、可重填,
  已删行在列表/两视图/详情四处置为不可见(V30/V32 我的面半边)
- 两视图谓词分离:代填行只进操作人 submitList 与被代填人 aboutList,
  自填行(subject=本人)两视图都在;零模块授权学生两视图照常(与数据管理面相反)
- myDetail 归属:提交人与被代填人都可读,返回钉住版本 groups + 值文档,外层无库内自增 id(V5);
  升版后老行仍按 v1 呈现与校验(P20)
- V14 结构半边:本面无任何改状态端点(映射表断言死锁为六条)
- AnswerReadService.myDetail javadoc 修正:读路径没有 editable 字段,
  前端按 ItemView.submitterId 判 isMine,拒绝在 editBySubmitter/softDeleteBySubmitter(P75 附带项)

门禁:mvn -B -o clean test → Tests run: 190, Failures: 0, Errors: 0, Skipped: 0(原 178 + 本批 12)
2026-10-15 10:29:39 +08:00
chenxing a4b185b1bb docs(T5): 回填 §4 推送与 CI 证据(front run 1496 / service run 1497 均 completed/success)
build / build-push (push) Successful in 54s
2026-10-14 20:42:48 +08:00
chenxing 2c68d6b9f4 feat(t5-shell): T5 前端壳执行报告 + service 两处验收遗留小改
build / build-push (push) Successful in 2m19s
- 勘误③真落:docs/T3-report.md §2 V32「两个不同教师」补注(实为两个 roleId=7 操作者,教师(1) 按 P21 无代填权)
- P47 收紧:FormModuleKeys.anchorCustomIds 只认 path 空节点的 customId,DataScopeService.hasScopeAllAnchor 切过去;补「带 path 的导航行误配 fc:scope:all 不升级范围」用例(mvn -o clean package 178 例全绿)
- docs/T5-report.md:T5 做了什么 / 逐条自验证据 / 问题单 P48~P53 / 两仓推送与 CI
2026-10-14 20:36:31 +08:00
chenxing c0a0df47c1 docs(T4): §4 补记回填提交 2224523 的 CI run 1494(回填到此为止,不再追加自指提交)
build / build-push (push) Successful in 52s
2026-10-14 19:08:27 +08:00
chenxing 2224523a3f docs(T4): 回填 §4 推送与 CI 证据(run 1493 completed/success)
build / build-push (push) Successful in 52s
docs-only:只改本文,不动任何构建输入;同一提交说明 local-debug/ 冒烟脚本不在本仓工作树
(仓库根是 service/),以及 CI 取证改从已存凭据读 Gitea Actions API(git credential fill 在非交互会话会挂)。
2026-10-14 19:06:18 +08:00
chenxing 49b095eb81 feat(read): T4 后端·检索统计+数据范围+导出
build / build-push (push) Successful in 1m18s
四条读出路径(列表/详情/统计/导出)与管理动作行级把关共用 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
chenxing b2778e5d86 docs(T3): 回填 §4 推送与 CI 证据(run 1481 completed/success)并补 P33
build / build-push (push) Successful in 52s
P33 是本轮自查出的真实缺口:subject_id 只校验了授权与校域,未校验存在性
(要查 UserBase 的人,与 T4 的 SDK 调用链一起做);当前它是未经证实的引用。
2026-10-14 16:53:05 +08:00
chenxing bff0fb15fa feat(answer): T3 后端·答卷核心——提交/幂等/限次/索引/软删留痕 + 平台标准版上传
build / build-push (push) Successful in 52s
答卷域是"校验只写一遍、索引只有一份"这条选型(D2 模型 2b)的兑现处,本轮把它一次落齐。

- 提交链路按语义排死顺序:钉版解析→截止→幂等命中→subject 授权→值校验→附件同校归属
  →fill_quota 占位→落 answer→写索引。幂等必须在限次之前(重放那条本就占着名额),
  限次不做先 count 再插而是事务内撞 uk 即拒(并发下 count 靠不住,方案 §5.4)
- 钉版与定义域共用同一条 resolve 遮蔽实现;升版窗口旧版仍可填并钉旧版(P20),
  请求体不含 version/schId/submitterId——身份结构性不可注入(V17)
- FormValueValidator 按契约 §3 逐类校验值形状:date 严格 YYYY-MM-DD 且必须是真实日期、
  number 空值只认 null、选项值取内联 ∪ allowedValues(含软删条目)、
  附件只认 {id,fileName,key} 且拒绝对 URL/跨目录 key
- answer_index 只写显式 searchable 字段、多值一行一个、v_str 截 191;编辑按钉住版本重建,
  软删同步删行且释放 fill_quota 名额;留痕只增不改,改状态无任何转移规则校验(V15)
- 上传模块自写平台标准版:FILE_PATH 三层兜底链、随机存储名、平台 15 项黑名单+svg、
  ≤50MB(并按 P12 显式配 spring.servlet.multipart,默认 1MB 会挡掉合法附件)
- 数据面刻意不建列表/详情/统计/导出读接口:四条读出路径必须共用 T4 的 scope 构造器,
  现在写一个无范围过滤的详情接口就是给 T4 留后门
- T2 已验收文件仅按裁决改动:FormAdminGuard 收紧为 {2,7}(P21)并加 canManage;
  FormDefService 抽出 resolvePublished/新增 pinnedForData(数据面溯源含软删表单);
  FormRepository 加数据面取域查询;application.yml 只加 multipart 两行

自验:mvn -B -o clean package 绿(121 测试,本轮新增 54 条含 39 条打真实 MySQL,
V32 并发面用双线程非回滚 IT 取证),逐 V 证据与问题单 P23~P32 见 docs/T3-report.md
2026-10-14 16:50:07 +08:00
chenxing 90f6b0a767 docs(T2): 回填 §4 推送与 CI 证据(service run 1468 completed/success,front 零改动)
build / build-push (push) Successful in 1m46s
同时补记 CI 取证方式(本地凭据调 Actions API,凭据不回显不落盘)与
local-debug 目录不在两仓工作树内的事实。
2026-10-14 15:27:20 +08:00
chenxing 03f6a971c3 feat(definition): T2 后端·定义域——版本模型、schema 校验器、选项字典与另存副本
build / build-push (push) Successful in 2m22s
定义与答卷物理分离的第一步:把"契约"落进可执行代码,让后续答卷域只需消费不必再解释。

- form/form_version 头表+版本行:已发布行零写路径(改版=v+1 新行,老行逐字节冻结),
  升版期间头表指针不动,使契约 §5 的钉版与 §3.2 的两层缓存声明天然成立;uuid 建草稿即生成
- FormSchemaValidator:19 项属性白名单、14/11 控件、options 只认 {label,value} 数组或
  dict:{code} 引用(字符串数组被拒)、searchable 只出显式声明字段、跨版本同 key 不得换 type
  (否则 answer_index 分类型落值的语义与"删字段后历史仍可筛"当场失效)
- 选项字典两级:管理面同层精确匹配(校级改不到平台级)、解析面 {会话校,0} 且本校遮蔽不回落;
  value 物理不可变而 label 可改;软删条目仍进提交放行集;引用是活引用、只在读路径内联展开
- 表单级配置(audience/fill_limit/deadline 补 23:59:59/报名号/状态定义)留在头表,不随版本快照
- 另存为副本:新 code+新 uuid+version 1、schema 深拷贝且字典引用随行,源表只读
- 出入参不出现库内 id;手写 DDL 落 sql/t2_definition_domain.sql 并按 MySQL 5.7 复核(无 native SQL)

自验:mvn -B -o clean package 绿(67 测试,含 27 条打真实 MySQL 的 IT),逐 V 证据与 9 条
实现期问题单见 docs/T2-report.md
2026-10-14 15:23:35 +08:00
chenxing 5c5abe1e91 docs(T1): 回填 §4 推送与 CI 证据(service run 1456 / front run 1457 均 success)
build / build-push (push) Successful in 51s
2026-10-14 12:23:44 +08:00
chenxing 8c218b7271 feat(skeleton): T1 后端工程骨架——fork flow-center 模板
build / build-push (push) Successful in 44s
- 包 com.yunhai.formcenter:config / controller(api|oapi) / entity / enums / model / repository / service / common
- pom:剥 aviator、加 easyexcel 3.3.2、finalName=form-center、surefire 收 *Test+*IT(CI 不跑单测→本地 mvn test 是门禁)
- FormBaseController 平移 FlowBaseController:/api/base/GetUserInfo + GetUserMod,新增可选 target appCode(H4 未上线→空树+warn,不回落本应用树)
- CORS 与 yml 分层照搬:jar 内只留本地最小默认,端口/ctx/DB/SSO/redis 由 projman env 注入;notice 的 upload-path 容器锚定教训注释原样抄入,FILE_PATH 本地兜底键保留
- 自验:mvn -o clean package BUILD SUCCESS + 7 单测全绿 + 本地启动 /actuator/health 200(/api/* 302 CAS 墙、/oapi/* 401 验签墙)
- docs/T1-report.md:产出说明 + 13 条问题单
2026-10-14 12:21:10 +08:00
admin c62f5d9187 ci: init Dockerfile [skip ci] 2026-10-14 11:41:44 +08:00
admin d4fca4b636 ci: init build workflow [skip ci] 2026-10-14 11:41:43 +08:00
admin 63f7d7a586 Initial commit 2026-10-14 11:41:39 +08:00