Files
chenxing 2c68d6b9f4
build / build-push (push) Successful in 2m19s
feat(t5-shell): T5 前端壳执行报告 + service 两处验收遗留小改
- 勘误③真落: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

144 lines
22 KiB
Markdown
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.
# T3 执行说明 · 后端·答卷核心
> 任务书:《表单中心任务书-Qoder-20261014.md》v1.1 §T3|执行日期:2026-10-14|执行方:Qoder
> 输入权威:方案 v1.4(§4.3②/§5.2/§5.4/§7.7)+ 契约 v1.0(§3/§5/**§2.2 实现期补充定稿**/§6 **错误码四段定稿**/§10)+ 手册 §5 裁决 **P5/P10/P12/P14/P18/P19/P20/P21/P22**
> 前置:T1/T2 已验收。本轮对 T2 已验收文件只做**三处裁决驱动的扩展**(见 §1.6),未重构其行为;T2/T1 的 67 条测试原样通过。
---
## 1. 做了什么
### 1.1 新增文件(答卷域:6 实体 + 6 仓库 + 4 服务 + 2 控制器 + 1 校验器 + 1 DDL 脚本 + 7 测试类)
| 包 | 新增 | 承担 |
| --- | --- | --- |
| `entity` | `Answer` / `AnswerIndex` / `StatusLog` / `AnswerOpLog` / `FillQuota` / `FormFile` | 契约 §10 答卷域五表(`MEDIUMTEXT`、`v_str(191)`、`DECIMAL(20,6)`、`uk_answer_uuid`/`uk_answer_idem`/`uk_quota` 逐项对齐)+ 附件落档行(P24) |
| `repository` | 六个同名 `*Repository` | 全部 JPQL/派生查询,**零 native SQL**(P10);**answer 数据无物理删除通道**(P22/V30)——`AnswerRepository` 不提供删行方法;`FillQuotaRepository.releaseQuota` 的删除=**释放名额**(方案 §5.4 点名行为,V32 后半),不是答卷数据删除〔措辞按 T3 验收报告 §4.1 于 T4 更正〕 |
| `common` | `FormValueValidator` | 契约 §3 值形状 ×11 放行类的提交期校验;附件值只认 `{id,fileName,key}` 且拒绝对 URL;字典候选由服务层注入(保持可脱库单测) |
| `service` | `AnswerService`(提交编排)/ `AnswerIndexService`(索引写/重建/清理)/ `AnswerAdminService`(编辑/软删/改状态/批量)/ `FormFileService`(上传) | 见 §1.2–§1.4 |
| `model` | `AnswerModels` | 提交/编辑/批量/上传出入参;请求体**没有** `schId/submitterId` 字段,响应只出现 `answerId`(uuid) |
| `controller/api` | `FormAnswerController`(2 端点)+ `FormAnswerAdminController`(5 端点) | 见 §1.5 |
| `sql` | `t3_answer_domain.sql` | 答卷域六表 MySQL 5.7 兼容 DDL(评审权威 + 生产 `ddl-auto=none` 升级底稿) |
| `local-debug` | `t3-answer-smoke.sh` | 手册 §4.2 |
### 1.2 提交链路(契约 §5;顺序即语义,写在 `AnswerService` 类注释里)
```
钉版解析 → 截止 → 幂等命中 → subject 授权 → 值校验(按钉住版本) → 附件同校归属
→ fill_quota 占位(撞 uk 即拒) → 落 answer → 写 answer_index(只写 searchable)
```
三处顺序是有意为之,不是随手排的:
1. **幂等在限次之前**:重放的那一条本来就占着名额,先查限次会把"双击重试"误判成"超过次数";
2. **限次用"事务内插入 + uk 撞键即拒"**(方案 §5.4 v1.4),不做"先 count 再插"——count 在并发下靠不住;
`fill_quota` 与 `answer` 同事务,答卷插入失败则名额一起回滚,不留"名额被占但无答卷"的脏状态;
3. **值校验用未展开的 schema 原文 + `allowedValues`(含软删条目)**,而不是 resolve 的内联结果:
展开后就看不出字段引用了哪个字典,"软删条目仍放行"(契约 §2.1/V38)无法表达。
**钉版**:`FormDefService.resolvePublished` 与填报页 resolve 走**同一条遮蔽实现**(本轮把它从 `resolve()` 里提出来复用),提交时解析到的 `current_version` 即加载版;配合 P20(升版只开草稿行、指针不动),"升版瞬间在途提交按旧版安全落地"天然成立。请求体**不接收** version(契约 §5 请求字段是闭集)。
**身份**(V17):`sch_id`/`submitter_id` 只从会话 `BoaUser` 取,`SubmitRequest` 里没有这两个字段(客户端即便多传也被 Jackson 忽略);`subject_id` 非空且 ≠ 本人 → 要求管理权(P21 闸门),否则 FORBIDDEN。
### 1.3 索引与状态(方案 §5.2/§5.3)
- `AnswerIndexService`:只写显式 `searchable` 字段;checkbox/multiselect **一值一行**;`number→v_num`、`date→v_date`、文本/选项 `→v_str(截 191)`;`upload/image` 无检索算子定义 → 不写行只 warn(P28)。
- 三个调用点:提交写入 / 编辑 `rebuild` / 软删 `clear`。**不存在**"按新版本 schema 清理旧字段行"的入口——那会打掉 V8 的历史可筛。
- 初始状态 = 表单 `status_defs` 里第一个未废弃 code(未配置即 `pending`)。
### 1.4 上传模块(平台标准版,未抄 notice FileController)
- 存储根读取形 `${FILE_PATH:${FILE_UPLOAD_PATH:${file.upload-path:/data/upload}}}`(yml 注释里定死的那条链;启动日志打印 `store_root=`,IT 断言兜底链生效);**yml 的 notice 教训注释一字未动**。
- `key = form/yyyy/MM/{SecureRandom 32hex}.{ext}`:存储名与用户文件名完全无关 → 同名不覆盖、路径穿越被吸收成展示名、伪装双扩展名按最后一段判定。
- 黑名单 = 平台同源 15 项(jsp/jspx/html/htm/js/exe/sh/bat/php/asp/aspx/jar/war/class/htaccess)**+ `svg`**(P27);扩展名形态限 `^[a-z0-9]{1,15}$`。
- 返回值字段闭集 `{id, fileName, key}`;库里也只存相对 `store_key`(varchar(191),本列有唯一键,避开 5.7 的 767 字节单列索引上限)。
- **P12 已配**:`spring.servlet.multipart.max-file-size/max-request-size=50MB`(默认 1MB 会直接挡掉合法附件),服务侧再判一次 ≤50MB(容器上限与业务上限分离,报错信息可读)。
### 1.5 接口清单(本轮 7 个新端点,累计 28)
- 用户面:`POST /api/form/submit`(幂等命中仍 200+原 `answerId`)、`POST /api/form/upload`(单文件)。
- 数据管理面 `POST /api/form/admin/data/*`:`edit`、`softDelete`、`changeStatus`、`batch/softDelete`、`batch/changeStatus`。
- **本轮刻意不建**:列表/详情/统计/导出读接口——方案 §6.4 红线要求四条读出路径共用同一个 scope 构造器,那是 T4 的建成物;先写一个无范围过滤的详情接口就是给 T4 留越权后门。
- **也不存在**填表人可用的编辑/撤回端点(V29 的"404/403"里,404 由端点不存在保证、403 由守卫保证)。
### 1.6 对 T2 已验收产出的三处改动(全部裁决驱动,非重构)
| 文件 | 改了什么 | 依据 |
| --- | --- | --- |
| `FormAdminGuard` | 校级角色集 `{2,5,7}` → **`{2,7}`**;新增 `canManage()`(不抛异常版,供 subject 授权分支);平台级拒绝判定前移(原因说准) | **P21 收紧**(局领导属查看角色,不给定义/数据管理权)+ 契约 §5 代填分支 |
| `FormDefService` | `resolve()` 抽出 `resolvePublished()`(返回头表+版本行,供提交钉版复用同一条遮蔽实现);新增 `pinnedForData()`(数据面解析,不校验生命周期、含软删表单、遮蔽歧义时退平台级行);`answerNoEnabled=1` 时校验 `code ≤49`(`answer_no varchar(64)` 装得下号) | 契约 §5 钉版、§5.4 数据读改不受停用/软删影响、§10 列宽 |
| `FormRepository` | 新增 `findDataDomainCandidates`(不排除软删表单的取域查询) | 方案 §5.4"软删是唯一切除形态、无物理删除"→ 答卷溯源不能断 |
| `application.yml` | 只加 `spring.servlet.multipart` 两行 | **P12** |
### 1.7 环境动作(本机副作用)
- 复用本地 MySQL 8.0.45(3306)+ 库 `bsch_formcenter`;`ddl-auto=update` 新建答卷域六表(`form_file.store_key` 后由 255 手工 `ALTER` 到 191 以对齐实体,表当时为空,无数据风险)。
- 为验证 `sql/t3_answer_domain.sql` 可执行,临时建库 `fc_t3_ddlcheck` 实跑(`mysql_exit=0`、6 表齐),**验证完已 DROP**,`SHOW DATABASES LIKE 'fc_t3%'` 为空。
- 上传测试写入 `service/target/tmp-upload/`(`mvn clean` 带走;`target/` 已被 .gitignore 排除),**未写 `D:/data/upload`**——冒烟启动时未注入 env,兜底键生效才写到那里,本轮该路径下只有脚本外无手工写入(T1 起该目录即本地上传兜底根)。
- IT 数据零残留:`answer/answer_index/fill_quota/answer_op_log/status_log/form_file/form` 计数全为 0(并发 IT 非回滚,靠 `@AfterEach` 定点清理,已验证)。
- 未启动 CAS/UserBase/Redis;未触碰 160 共享环境。
---
## 2. 自验证据(逐 V)
**门禁**:`mvn -B -o clean package` → **BUILD SUCCESS,Tests run: 121, Failures: 0, Errors: 0, Skipped: 0**,产物 `target/form-center.jar`。
其中 **T3 新增 54 条**(`FormValueValidatorTest` 12 + `FormAdminGuardTest` 5 + `AnswerSubmitIT` 17 + `AnswerAdminIT` 11 + `FormFileServiceIT` 7 + `AnswerConcurrencyIT` 2),**39 条打真实 MySQL**;T2 的 67 条一条未改仍全绿。
| V | 断言(方案 §9 原文) | 证据 |
| --- | --- | --- |
| **V17** | `submitter_id`/`sch_id` 由会话推导、接口不接受外部传入;越权构造被拒;**普通用户把 `subject_id` 构造成他人被拒(仅可=自己,管理员代填不受此限)** | `AnswerSubmitIT.subjectAuthorization`(构造成他人→"无权为他人填报";=自己→放行且 `submitter=subject`)、`adminCanFillOnBehalf`(管理员代填:`submitter=教师`、`subject=学生`两键分列)、`sessionRequired`(未登录/无学校归属各自被拒)、`idempotencyKeyOwnedByAnotherAccountIsRejected`(跨账号撞库级幂等键不给他人 answerId);权限层 `FormAdminGuardTest`(5 条,含 P21 的 roleId=5 被移出)。请求体无 `schId/submitterId` 字段=结构性不可能外部传入 |
| **V29** | 填表人无编辑/撤回接口可用(404/403);管理员编辑成功且写 `answer_op_log` | 404:`FormAnswerController` 只有 `submit`/`upload` 两个端点(结构即证据)+ `FormAnswerAdminController` 的守卫;403:`FormAdminGuardTest.onlyRoleIdsTwoAndSevenManage`。`AnswerAdminIT.adminEditWritesOpLogAndRebuildsIndex`:编辑成功、`values_json` 更新、`op_log` 恰一行 `op=edit`+操作人正确、**身份列与状态列不动**、索引按钉住版本重建(`v_num` 变成 88);`editStillEnforcesValueContract` 证明被拒时**不落** op_log、原值不被破坏 |
| **V30** | 软删后列表/详情/统计/导出均不可见,索引行同步删除;无物理删除接口存在 | `AnswerAdminIT.softDeleteIsTheOnlyDeletion`:`is_deleted=1`、`answer_index` 行清空、写 `op_log`、`answerRepository.findById` 仍在(物理行还在)、重复删被拒。"四读出路径不可见"依赖 T4 读接口(本轮刻意不建,见 §1.5);**无物理删除**=两个仓库都不提供 delete 方法、控制器无端点(`AnswerRepository` javadoc 成文)。`batchSoftDeletePerItemLogging` 覆盖批量同口径 |
| **V31** | 同幂等键重复提交只产生一条答卷 | `AnswerSubmitIT.idempotencyReturnsOriginalAnswer`:两次同键 → 库里 1 行、第二次 `answerId` 相同且 `replayed=true`、**原值不被第二次的值覆盖**;`idempotencyKeyRequired`(必填/≤64/不同键各成一条);并发同键见 V32 |
| **V32** | 限一次:subject 非空按 subject 限(两老师代填同一学生第二条被拒)、subject 空按 submitter 限(同人第二次被拒)——**两种 limitScope 各验一条**;**并发同键双提交恰一条成功(fill_quota 唯一键兜底)**;**软删后可重填(名额已释放)**;不限次表单不受影响 | 功能面:`onceLimitIsPerSubjectWhenSubjectPresent`(**两个不同教师**(实为两个 roleId=7 操作者——教师(1) 按 P21 无代填权)代填同一学生→第二条"限填一次";换学生仍可填)、`onceLimitFallsBackToSubmitter`(同人第二次被拒 + 不限次表单两条都过)、`softDeleteReleasesQuota`(软删后同 subject 可重填)。**并发面**:`AnswerConcurrencyIT.concurrentSubmitsAgainstOnceLimitProduceExactlyOneAnswer`(真 MySQL 双线程同时放开:恰 1 条 `answer`、恰一条成功另一个拿到"限填一次"、`fill_quota` 恰 1 行 `limit_key=提交人id`、成功那条索引照常);`concurrentSubmitsWithSameIdempotencyKeyProduceOneAnswer`(同幂等键并发:恰 1 行,两个返回值指向同一 uuid——即"撞 uk 后回读原答卷"的并发结局) |
| **V33** | 截止后填报页只读、提交接口拒绝;停用表单入口失效但数据管理可见 | `deadlineBlocksSubmit`(2020 年截止→"已过收集截止时间";改 2099→放行,顺带证"只选日期补 23:59:59")、`disabledFormClosesFillEntryOnly`(停用后提交被拒"已停用",`enable` 后同表单同数据恢复可填)、`AnswerAdminIT.domainScopingAndSoftDeletedFormStillOwnsAnswers`(表单软删后其存量答卷仍可编辑=数据侧可见可管理) |
| **V14**(编排补充①) | 填表人改状态被拒(接口层) | `AnswerAdminIT.submitterCannotChangeStatus`:对学生(3)/教师(1) 身份调控制器实际走的 `adminGuard.requireAdmin` → FORBIDDEN"无表单管理权限",对管理员放行;数据面不存在"我改我这条的状态"的端点 |
| **V15**(编排补充①) | 改状态写 `status_log`;系统不存在任何流转规则校验 | `statusChangeHasNoTransitionRules`:`pending → back → pending → done`(含倒退)全部放行,`status_log` 三行按序、操作人正确;`statusMustBeLiveDefinition` 证明唯一约束是"目标值必须是未废弃定义",**不是**转移规则 |
| **V24**(编排补充②,本地可断言部分) | 非白名单扩展名被拒;答卷值中不含绝对 URL;返回只含 `{id,fileName,key}` | `FormFileServiceIT.dangerousExtensionsRejected`(15 项逐个 + `svg` + 缺扩展名 + 非法扩展名形态 + `report.pdf.jsp` 伪装双扩展名 + 放行 `ok.docx`)、`uploadReturnsContractTripleAndStoresUnderRoot`(响应字段闭集恰是三件、`key` 匹配 `^form/\d{4}/\d{2}/[0-9a-f]{32}\.jpg$`、库里也存相对 key)、`fileSizeCap`(>50MB 拒且不留行;空文件拒)、`submittedAttachmentIdsMustBeOwned`(提交值里 `key` 给 `http://cdn...` 被拒"相对存储路径";伪造附件 id 被拒)。**"经 FILE_WEB_PATH 可访问"按编排留给 T9 部署环境**;`FILE_PATH` 兜底链由 `storeRootFollowsDocumentedPlaceholderChain` 断言 |
| **V1/V3/V8/V9 相邻面** | 值形状按契约 §3 全类型接受;索引只写 searchable;升版字段身份 | `FormValueValidatorTest` 12 条(含 14 类里 11 类的示例值、附件形状、字典软删放行、上限 9);`AnswerSubmitIT.indexWritesSearchableOnly`(`remark` 不声明就不进索引、250 字符截到 191、`v_date`/`v_num` 类型正确)、`submitPinsCurrentlyPublishedVersion`(v2 字段在 v1 期被拒"未知字段",发布后按 v2 钉版) |
**其它取证**
| 项 | 结果 |
| --- | --- |
| MySQL 5.7 复核(P10) | ①答卷域**零 native SQL**;②`sql/t3_answer_domain.sql` 在 `fc_t3_ddlcheck` 实跑 `exit=0`、6 表齐、随后 DROP;③`SHOW CREATE TABLE` 逐列对齐契约 §10(`mediumtext`/`v_str(191)`/`decimal(20,6)`/`date`/两个 uk + `uk_quota` + 四个 answer_index 复合索引);④`form_file.store_key` 由 255 主动收到 **191**(该列有唯一键,避开 5.7 单列索引 767 字节上限);⑤answer_index 复合索引键长约 1292B,依赖 5.7 默认 `ROW_FORMAT=DYNAMIC`+`innodb_large_prefix=ON` → 记 **P31** 待 160 核 |
| 启动冒烟 | `bash local-debug/t3-answer-smoke.sh` → **7/7 PASS**:health=200;`store_root=D:/data/upload`(兜底链生效);`/api/form/submit`、`/api/form/upload`、`/api/form/admin/data/edit`、`.../batch/changeStatus` 无票均 302→CAS;`/oapi/*` 仍 401 |
| 错误码口径(P14 定稿) | 全线沿用四段:值形状/未知字段/附件伪造/未找到=`PARAM_ERROR(2)`;未登录=`UNAUTHORIZED(4)`;subject 越权/无管理权=`FORBIDDEN(3)`;限次/截止/未发布/已停用/无草稿=`CUSTOM(5)`。本轮未引入任何 VALIDATE/NOT_FOUND 字样 |
| 登录态冒烟(P1 遗留) | 仍无本地 CAS/UserBase,接口以"墙后 302 + 54 条服务层直连真库"替代取证;缺口沿 P1 归 T5 联调轮 |
---
## 3. 问题单(本轮登记,供执行手册 §5 裁决;编号接续 P22)
| # | 问题 | 本轮取值(最保守解读) | 影响面 |
| --- | --- | --- | --- |
| P23 | 契约 §10 `uk_answer_idem(idempotency_key)` 是**库级**唯一键(不含 `sch_id`/`submitter_id`),跨账号撞同键的语义契约未写 | 命中同键且归属(会话校+提交人)一致 → 200+原 `answerId`;**归属不一致 → FORBIDDEN**(不把他人 answerId 回给请求方,uuid 不可枚举但同键命中本身就是探测面);并发撞键由编排在事务外回读原答卷。若裁决要"键只需表单内唯一",把 uk 改 `(sch_id, code, idempotency_key)` | 幂等语义与并发测试 |
| P24 | 契约 §3 附件值形状含 `id`,但 §10 九表**没有附件表**——`id` 无处解析 | 建最小 `form_file`(`id/sch_id/file_name/store_key/file_size/ext`,`uk(store_key)`),上传成功即落档;值里的 `id` 即本表主键,提交时做**同校归属校验**。若裁决"中心不落附件档、`id` 由消费方自造",本表与归属校验一起退掉 | 上传模块 + 二期 GC/鉴权下载接口 |
| P25 | 契约 §1 校域不变式使 `answer.sch_id` 恒=会话校,于是答卷的钉版坐标 `(校, app_code, code, version)` **不含"表单归属校"**:平台级表单被本校后建的同 `(app_code,code)` 表单遮蔽后,历史答卷版本行解析有歧义窗口 | `pinnedForData()` 在本校域取不到该版本行时,**退回平台级同 code 表单的行**(历史数据仍按原 schema 校验,不静默失败)。根治=`answer` 增列 `form_uuid`(或契约明确"平台级表单答卷必须存平台级归属") | 数据面溯源;T4 读详情/导出同样要走这条解析 |
| P26 | `answer_no` 当日序号取"当日行数+1",**并发下可重号**;契约 §10 未给 `answer_no` 任何唯一键 | 按 DDL 草案执行:不加索引、不改格式,序号含软删行(软删不回收号)。要根治需 `uk(sch_id, code, answer_no)` + 撞号重试,或独立取号表——属加语义,等裁决 | 报名号场景(第一天就会并发);T9 压测可暴露 |
| P27 | 上传黑名单:平台同源清单(svc-hall/UserBase/Notice 那 15 项)不含 `svg`,而一期"key=不可猜测随机串即访问控制"(方案 §7.7)对 svg 失效(同源直访 + 内嵌脚本=存储型 XSS 面) | **在平台 15 项上追加 `svg`**,代码注释写明理由。若确有图标上传需求,需二期的鉴权下载接口(U11 族)才能安全放行 | 安全边界;前端 T7/T8 若允许 svg 需同步 |
| P28 | 附件字段勾了 `searchable` 时如何建索引,契约 §2 未给附件的检索算子(算子只定义了 等值/区间/前缀 三类) | **不写索引行**并 warn(不发明算子、不拿 fileName 冒充可筛值)。若要"N 个文件"可筛,需先定算子 | V3/V9 一致性;T4 筛选候选列表 |
| P29 | 批量操作单次条数上限,方案 §5.4 只写"批量逐条走单条校验与留痕" | 加安全阈值 **200**(逐条各走独立事务,一次请求挂几千条等于把接口变成 DoS 面);超限 `PARAM_ERROR`。批量与单条同形:`op` 用 `batch_*` 前缀(契约 §10 注释的 `edit/soft_delete/batch_…` 形态)、一条答卷一行日志、summary 带批次上下文 | 前端 T8 分页勾选即可满足 |
| P30 | 数据管理操作的行级范围(契约 §8 矩阵 / `fc:scope:all`)本轮未接 | 仍走 P21 过渡角色闸门 + `(域, uuid)` 定位(跨校答卷直接查不到);**行级 scope 与"编辑入口按数据范围收敛"归 T4**(V27/V39 的断言点)。本轮不做"半个 scope",避免与 T4 的构造器双份实现 | T4 收紧点 |
| P31 | `answer_index` 三条复合索引键长约 1292 字节,超过 5.7 旧行格式的单索引 767 字节上限 | 依赖 5.7 默认 `ROW_FORMAT=DYNAMIC` + `innodb_large_prefix=ON`(上限 3072);`sql/t3_answer_domain.sql` 顶部把该前提与降级方案(`v_str` 降 `varchar(64)`,前缀匹配语义不变)写死。**160 生产行格式/版本待核**(P10 的延伸,方案 §7.7 记档项) | 建表能否成功;T9 部署前必查 |
| P32 | 编辑/改状态是否受"截止时间"与"表单软删"约束,契约只写了填报入口 | 编辑与改状态**不看截止**(截止只挡填报,方案 §5.4);表单软删后其存量答卷**仍可管理**(数据面取域不排除软删表单,新增 `findDataDomainCandidates`)。两条都按"停用/软删只断填报入口、数据读改不变"外推 | T4/T8 读路径需保持同一外推口径 |
| P33 | `subject_id` 的**存在性/归属**未校验:管理员代填可把 `subject_id` 写成任意整数(不存在的用户、或别校的用户 id),污染"关于我的"视图(V18 的主体可见性依赖 id 真实) | 本轮只校验**授权**(普通用户只能=自己,V17)与**校域**(`sch_id` 恒=会话校),不校验 id 是否存在于本校——需要 UserBase 查人(`by-ids`/组树),与 T4 的 SDK 调用链一起做。`subject_id` 因此当前是"未经证实的引用",**T4 接入时必须补一道 by-ids 校验**(越权面不大但数据会脏) | V18;T4 scope 与"关于我的"视图 |
---
## 4. 推送与 CI
| 仓 | 提交 | 推送 | CI run | 结论 |
| --- | --- | --- | --- | --- |
| form-center-service | `bff0fb1`(33 条:29 新增 + 4 处裁决驱动改动,行数见 §1.6) | `90f6b0a..bff0fb1 main -> main` | [run 1481](http://192.168.10.160:3000/form-center/form-center-service/actions/runs/1481)(`build.yml@refs/heads/main`,head_sha `bff0fb1…`) | **status=completed / conclusion=success**(Gitea Actions API 实测,poll1 即终态) |
| form-center-front | 无改动 | 未推送 | — | T3 是纯后端任务 |
- 推送方式:常规 `git push origin main`;暂存**按显式路径逐条 `git add`**(33 条路径,提交前 `git status` 核对:无 `add -A`、无敏感文件、`target/` 与 `local-debug/` 不在本仓工作树)。
- CI 取证方式:`git credential fill` 取本机已存凭据后调 `GET /api/v1/repos/.../actions/runs[/{id}]`,凭据不回显、不落盘、不入库。
- CI 只跑 `mvn -B -DskipTests clean package`(**不跑单测**,方案 §7.2),所以 CI 绿只证明产物链;
答卷的行为证据在本文 §2 的本地 `mvn test`(121 条,其中 39 条打真实 MySQL、2 条真并发取证)。
- 本节的回填本身是一次 docs-only 提交(只改本文,不动任何构建输入),其后同流水线再跑一次。
—— 全文完 ——