import fs from 'node:fs'
import path from 'node:path'
const dir='reports/guide-formal-20260910'
const esc=s=>String(s??'').replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]))
// 报告只保留结论,不携带完整接口载荷、内部存储地址或上传凭据。
for(const file of ['create.json','upload.json','publish.json']){
const data=JSON.parse(fs.readFileSync(path.join(dir,file),'utf8'))
for(const check of data.checks||[])if(check.detail?.startsWith('{'))check.detail='真实 HTTP 请求成功;响应内容已脱敏。'
fs.writeFileSync(path.join(dir,file),JSON.stringify(data,null,2))
}
const suites=[['create.json','正式工程新建'],['upload.json','模型资源上传与恢复'],['scenes.json','固定场景版本与修订'],['publish.json','正式发布与交付物'],['review.json','教学阅读及真实 HTTP 查询'],['permissions.json','权限、私人记录与反馈'],['final.json','阅读、笔记、进度及 HTTP 写入'],['template.json','正式模板新建'],['media.json','图片、视频及截图引用'],['offline-browser.json','正式离线包运行']]
const results=suites.map(([file,title])=>({file,title,...JSON.parse(fs.readFileSync(path.join(dir,file),'utf8'))}))
const checks=results.flatMap(r=>r.checks),passed=checks.filter(c=>c.pass).length
const release=JSON.parse(fs.readFileSync(path.join(dir,'release.json'),'utf8'))
const offline=JSON.parse(fs.readFileSync(path.join(dir,'offline.json'),'utf8'))
const shots=[
['01-formal-created.png','正式指导书新建,写入内容服务'],
['02-formal-assets.png','狐狸、牛奶卡车 GLB 上传至正式资源存储'],
['03-formal-fox.png','刷新后狐狸模型恢复显示'],
['04-formal-published.png','V3.0.0 正式发布及交付物'],
['05-reader-notes.png','教学阅读:笔记与收藏'],
['06-reader-layout.png','教学阅读:OFD 版式'],
['09-reader-model.png','教学动态阅读:狐狸模型'],
['10-two-scene-resources.png','两个已发布场景固定版本引用'],
['11-reader-truck.png','教学动态阅读:牛奶卡车'],
['12-reading-completed.png','完成阅读,进度与统计更新'],
['08-http-integration.png','对外接口:真实 HTTP 查询'],
['13-http-conflict.png','对外接口:旧版本写入返回 409'],
['14-formal-templates.png','正式模板库'],
['17-template-editor.png','模板生成章节与步骤并保存'],
['15-offline-fox-scene.png','离线包:场景 160 当前发布内容(维修车间与装备)'],
['16-offline-truck-scene.png','离线包:牛奶卡车场景'],
['18-formal-video.png','图片与截图引用保持,视频实际播放'],
]
const html=`
作业指导正式接口与教学闭环 · 实测报告
开发环境 · 2026-09-10 — 2026-09-11 · 顺序实施与回归
作业指导正式接口与教学闭环
已完成编辑器接入、教学阅读记录、真实 HTTP 联调三项实施。正式发布工程 ${esc(release.projectId)},版本 ${esc(release.versionCode)}(版本 ID ${esc(release.versionId)})。
${passed}/${checks.length}E2E 检查点通过
391后端测试通过
17关键截图
3保留复核工程
实施内容
| 顺序 | 落地结果 | 验证方式 |
|---|
| 1. 内容、资源、版本 | 完整编辑文档写入正式内容服务;上传资源进入受控存储;模板、模型和场景从正式接口加载;保存、发布、历史版本及恢复使用服务端版本。场景资源固定到发布版本,修改草稿不改变已发布教学内容。 | 新建、模板生成、保存重开、GLB/图片/视频上传、固定场景引用、修订与发布、OFD 和离线 ZIP 实际下载。 |
| 2. 教学阅读闭环 | 教学端消费正式发布文档与受控资源;阅读位置、笔记、收藏按用户和发布版本独立保存;记录开始/完成、反馈与处理,提供授权聚合统计。 | 翻页恢复、笔记增改删、收藏取消、动态步骤同步进度、学员权限及数据隔离、反馈回复、完成记录。 |
| 3. 真实 HTTP 联调 | 接口页面通过统一 HTTP 客户端调用 Gateway/业务服务,展示真实响应。涵盖内容、资源、版本、教学、个人阅读记录、统计与交付物。 | 浏览器实际 GET/PUT;旧版本返回 409;错误方法返回 405;未经授权内容和统计接口返回 403。 |
接口沿用平台统一登录会话与权限体系。第三方系统需取得平台会话;数字人开放应用 APPID 不用于这组作业指导接口。
本轮修复
- 完整文档校验及正式资源 URI 保留;教学投影转换为受控下载地址,避免文件存储地址直接暴露或刷新后丢失。
- 大 GLB 流式下载被默认 30 秒中断;异步流响应超时调整为可配置的 30 分钟。
- 保存与发布并发造成旧版本冲突;发布前等待自动保存队列完成。
- 阅读器初始化零页记录、只读目录/动画选择被拦截、动态页进度未同步、笔记删除后编辑框残留。
- 离线模型切换后的矩阵与镜头适配错误,导致场景偏出画面。
- 图片、视频海报的正式 URI 被过滤;视频完成加载后动态预览未刷新。已验证受控视频实际播放。
- 发布弹窗与动态白纸内容的暗色主题文字对比度。
测试结果
浏览器为 Edge / Playwright,主要视口 1920×1080。管理操作使用内部维护账号;教学权限使用临时学员账号,测试后已清理。HTTP 通过 Web 6180 → Gateway 6100 → Tran 6102。
mvn -pl ut_tran -am test package:391 项通过,0 失败、0 错误、0 跳过。
pnpm run build:类型检查、Vite 构建与模型编辑器发布资源校验通过;保留既有 chunk 大小及运行时资源 URL 警告。
${results.map(s=>`${esc(s.title)} JSON 证据
| 检查点 | 结果 |
${s.checks.map(c=>`| ${esc(c.name)}${c.detail?` ${esc(c.detail)} `:''} | ${c.pass?'通过':'失败'} |
`).join('')}
`).join('')}检查点来自分阶段脚本,覆盖关键链路,不表示所有边缘输入均已穷举。create/scenes 等早期证据对应创建及 V1→V2 阶段;最新发布、阅读和离线回归对应 V3。
关键截图
点击图片查看原图。文件名中的“fox-scene”沿用测试场景名称,不代表该场景当前实际包含狐狸。
${shots.map(([file,title])=>`
${esc(title)}`).join('')}
交付与迁移
下载实测正式 OFD · 下载实测正式离线 ZIP(本地文件) · 发布摘要 · ZIP 完整性证据
离线 ZIP:${Number(offline.zipBytes).toLocaleString('zh-CN')} 字节;4 个 GLB 文件通过头部与长度检查。从正式 V3 下载后解压并运行,禁止访问业务服务及公网仍能加载两个场景。镜头修复代码已经包含在正式交付包中。
SHA-256:${esc(offline.sha256)}
| 开发环境迁移 | 说明 |
|---|
| 20260826_017_teaching_guide_reading_closed_loop.sql | 补执行既有阅读与反馈表迁移,先回滚试执行再正式执行。 |
| 20260826_018_content_guide_template_library.sql | 补执行既有正式模板库迁移,保留 4 个模板。 |
| 20260911_001_guide_reader_state.sql | 新增按用户/项目/发布版本隔离的阅读位置、笔记、收藏及进度表;完整 DDL、数据库设计、迁移记录和 Wiki 已同步。 |
其他环境部署前先检查既有表,避免重复执行。新增流下载配置 UT_CONTENT_STREAM_TIMEOUT 默认 30m。本轮不代表其他环境已完成数据库迁移。
复核说明与保留数据
- 工程 412:正式闭环-狐狸与卡车指导书-20260910,已发布 V3.0.0 / 472。
- 工程 413:正式模板新建回归-20260911,保留草稿。
- 工程 414:正式图片视频资源回归-20260911,保留草稿。
- 临时学员账号已清理,正式测试阅读及反馈历史保留。阅读页进度与“完成阅读”分别记录,不等同于考核成绩。
- 既有内部维护账号头像存在单独的 404,属于原账号头像资源问题;本轮指导书资源、视频及交付下载检查通过。没有将该请求计入“无网络错误”的结论。
- 本轮未自动提交或推送 Git。大型测试 ZIP 仅保留在本地报告目录,不纳入 Git。
需要核对的数据差异:场景 160@463 名为“实测场景-狐狸野外训练区-20260905”,当前发布内容实际是维修车间与装备,不包含狐狸。已按其固定发布版本验证,未修改源场景;狐狸模型在指导书独立步骤中验证通过。牛奶卡车场景使用 159@209。
三项实施均在本次授权范围内,没有新增待批准的大幅功能调整。旧浏览器本地工程如需迁移,使用显式导入新工程;无法从服务端恢复的本地文件需重新上传。
`
fs.writeFileSync(path.join(dir,'index.html'),html)
console.log(`Report generated: ${passed}/${checks.length} checks, ${shots.length} screenshots`)
if(passed!==checks.length)process.exitCode=1