写在前面
今天这篇不写日常简报,改写成一篇完整的项目记录。
这一天的核心工作,是把一套原本偏向在线运行的 scratch-gui,逐步改造成一套适合本地校园环境使用的图形化编程系统。最终目标很明确:
scratch-gui可以在本地目录中编译并通过浏览器访问。scratch和现有本地 Web 根目录整合到一起。- 角色、背景、帮助页、素材等尽量本地化,不再依赖外网。
- 增加一个学生上传 Scratch 源文件、老师登录管理的
share页面。 - 上传后的
.sb/.sb2/.sb3文件可以直接在本地 Scratch 编辑器中打开。 - 整个系统最终可以通过
http://127.0.0.1/share/和http://127.0.0.1/scratch/build/这种朴素地址稳定访问。
这篇记录会尽量把做过的事情、关键文件、踩过的坑和最终修法都整理下来,方便以后自己维护,也方便后面继续扩展。
一、目录结构与整体思路
最终落地在本地 Web 根目录 /Users/jarod/Sites 下,核心结构如下:
/Users/jarod/Sites
├── scratch/ # scratch-gui 工程与 build 产物
├── share/ # 学生上传 / 老师管理 PHP 页面
├── briefings/ # 博客与简报记录
├── index.html
└── index.php
其中两块是主角:
scratch/:负责编辑器本身share/:负责作品上传、列表展示、老师管理、点开作品进入编辑器
一开始的思路是“先让它能跑”,后面逐步演进为“让它稳定可用、资源尽量本地化、尽量少依赖外部服务”。
二、先把 scratch-gui 编译并接入本地站点
1. 编译与运行
最开始先把 scratch-gui 放进本地目录中,完成依赖安装和构建。早期阶段用过开发服务器,也做过静态 build 输出,后面为了更好地和本地站点整合,最终选择以 scratch/build 静态构建产物为主。
实际使用时访问入口统一落在:
http://127.0.0.1/scratch/http://127.0.0.1/scratch/build/
2. 和本地 Web 服务整合
为了不让 Scratch 成为一个独立端口服务,后面把它整合进 /Users/jarod/Sites 的现有本地 Web 服务目录。这样目录更统一,后续 share 页面和 Scratch 编辑器之间的互相跳转也更简单。
这一步的价值很直接:
- 避免多个端口带来的跳转混乱
- 后续
share/open.php可以直接重定向到/scratch/build/ - 资源路径能稳定写成站内路径
三、把 Scratch 资源尽量本地化
1. 本地化素材库资源
Scratch 角色库、背景库、声音库,本来大量依赖线上资源。为了离线或内网环境可用,后面把素材镜像到了本地目录:
scratch/static/local-assets/
scratch/build/static/local-assets/
并且修改了资源请求路径,让素材优先从本地读取。
关键改动涉及:
2. 为什么后来加了 asset.php
一开始本地素材是直接仿照 Scratch 官方资产路径放到静态目录中,类似:
/scratch/build/static/local-assets/internalapi/asset/.../get
但本地 PHP/Apache 站点对这类“无扩展名结尾”的素材返回 Content-Type 时不够稳定,导致角色库里明明是 200,浏览器却因为 MIME 或资源解释问题不显示图片。
后来为了彻底绕过这个坑,增加了:
再把资源读取统一改成:
/scratch/asset.php?md5ext=xxxx.svg
这个改法的好处是:
- 所有素材请求都有明确 PHP 输出
Content-Type可以精确控制- 不再依赖目录名、
.htaccess、站点 MIME 猜测
3. 去掉外网帮助页和追踪依赖
还陆续把若干外部帮助链接、视频页、提示页改到本地说明页,避免编辑器运行时再去碰外网。
本地帮助页放在:
涉及过的文件包括:
这一步做完后,Scratch 角色库、背景库、扩展说明、帮助跳转都更像一个完整的本地系统,而不是“本地壳子 + 线上资源拼接”。
四、做一个本地 share 页面:学生上传,老师管理
1. 功能目标
用户需求很明确:
- 学生上传任意版本 Scratch 源文件
- 老师密码登录后台管理
- 支持中文文件名
- 支持按学生姓名或班级搜索
- 文件名可直接点击,在本地 Scratch 编辑器中打开
最开始我做过 Python 版原型,后面因为本地目录本身就是 PHP Web 服务目录,所以最终改为纯 PHP 版本。
2. 最终 share 结构
关键文件如下:
- index.php
- app.js
- styles.css
- lib/share-lib.php
- open.php
- public-file.php
- files.php
- api/upload.php
- api/uploads-public.php
- api/teacher-login.php
- api/teacher-uploads.php
- api/teacher-delete.php
3. 页面布局
后面又根据使用体验做了页面重排:
- 左侧:学生上传
- 右侧:作品列表
- 底部:老师登录 / 后台管理
这样比“上传区和列表堆在一起”清晰很多,尤其适合课堂现场快速操作。
五、中文文件名问题:为什么会变成 ______.sb3
这是一个很典型、也很容易被忽视的细节。
最初为了保证文件系统安全,上传逻辑直接把原始文件名做了 ASCII 清洗。这样一来,中文名自然都被替换成下划线,于是出现了:
校园作品.sb3 -> ______.sb3
后来做了拆分:
- 界面显示保留原始文件名
- 磁盘实际存储使用安全文件名
- 下载响应头同时写入普通
filename和 UTF-8filename*
相关代码主要在:
这个修正之后,新上传文件的中文名在前台、后台和下载时都能正常显示。
六、让上传作品能直接在 Scratch 编辑器中打开
这是整套系统里最折腾、也最有代表性的一条链路。
1. 设计目标
在 share 页面中点击文件名时,不是下载文件,而是直接进入本地 Scratch 编辑器,并把该项目打开。
所以有了这个跳转流程:
/share/open.php?id=xxxx
-> 302 跳转到
/scratch/build/?project_file=...&project_title=...
其中:
project_file指向真实项目文件project_title用于在编辑器标题栏显示
2. 关键桥梁文件
3. 这条链路踩过的坑
坑一:默认项目覆盖了上传项目
Scratch 在没有 hash 项目 ID 时,本来会自动加载默认项目。
当 project_file 方案接进来后,这条默认加载流程和远程项目加载打架,结果就是:刚加载的上传项目又被默认项目覆盖。
修法:
- 在 hash-parser-hoc.jsx 中判断:只要 URL 里带
project_file,就不要再走默认项目逻辑。
坑二:项目文件下载成功,但被当成“线上项目”继续保存
项目虽然已经通过 vm.loadProject() 打开,但后续 Redux 状态还把它当成“可保存的线上项目”,继续往自动保存分支走,导致编辑器状态异常。
修法:
- 在 query-parser-hoc.jsx 中把
project_file打开的项目视为“本地上传项目”,加载完成后不再触发自动保存流程。
坑三:另一个本地上传流程把远程打开误判成空上传
这是今天最关键的一处。
project_file 打开项目时,本质上也借用了“上传文件”的加载状态;但 SBFileUploaderHOC 监听到这个状态后,以为是一次没有选择文件的本地上传,于是直接调用取消逻辑,把正在加载的项目打回空白。
修法:
- 在 sb-file-uploader-hoc.jsx 中加判断:
如果当前 URL 带有project_file,就不要让本地上传组件去取消这次加载。
这一步修完后,/share 中上传的 .sb2/.sb3 文件终于能稳定打开。
坑四:绝对 URL 在不同访问方式下不稳定
最初 project_file 会生成带协议和主机的绝对地址,比如:
http://127.0.0.1/share/public-file.php?id=...
但如果实际是通过其他主机名、局域网地址或 HTTPS 访问,很容易出现协议判断不一致。
后面改成站内相对路径:
/share/public-file.php?id=...
相关修改在:
这样无论是 127.0.0.1、局域网 IP,还是别的本地域名,都能沿当前站点环境去请求项目文件。
七、角色库与背景库本地导入:缩略图正常不代表能导入
这块是另一个典型问题:看起来图片能显示了,但点了以后资源并没有真正进入编辑器。
1. 缩略图修复
角色库、背景库缩略图依赖的图片资源,后来都统一从本地 asset.php 输出。
这一步解决的是“能看到图”。
2. 角色导入修复
角色导入一开始尝试的是“把角色对象和 asset 数据直接喂给 vm.addSprite()”。
这个方式虽然能在一部分情况下工作,但不够稳,后期又出现了“角色库中的图片能看见,却不能加入编辑器”的回归。
最后的修法是:
- 先把角色和它依赖的 costumes / sounds 拉到本地
- 动态打成一个标准
.sprite3包 - 再走 Scratch VM 原生的
addSprite(spriteArchive)导入路径
关键文件:
这条路径的优点很明显:
- 更贴近 Scratch 自己“上传角色”的原生流程
- 避免对象兼容细节导致的导入不稳定
- 后续维护成本更低
3. 背景导入修复
背景和角色看似类似,实际不是同一条 VM API。
vm.addBackdrop(...) 对传入 asset 的要求更严格,所以后面单独给背景做了 runtime asset 构造。
关键文件:
这之后,背景库点击后就能把正确背景加入舞台,而不是只出现一个不完整的图片对象。
八、顶部导航与视觉小完善
在功能基本跑通后,还做了两个收尾动作:
1. 顶部“查看作品页面”按钮
将 Scratch 顶部的“查看作品页面”按钮改为直接跳到 /share/,并在按钮前面加入校徽图标。
相关文件:
这里还做过一次误改:一开始把左边“分享”按钮改掉了,后面已恢复原始分享逻辑,只保留右侧“查看作品页面”按钮改造。
2. 老师后台密码更新
老师登录密码后来调整为:
ghtxx.cn
对应哈希保存在:
九、这次用到的关键代码与资源清单
为了以后复查方便,这里把最关键的文件做一个归档。
Scratch 侧
- storage.js
- project-fetcher-hoc.jsx
- query-parser-hoc.jsx
- hash-parser-hoc.jsx
- sb-file-uploader-hoc.jsx
- library-assets.js
- sprite-library.jsx
- backdrop-library.jsx
- menu-bar.jsx
- community-button.jsx
- asset.php
- offline-help.html
Share 侧
- index.php
- app.js
- styles.css
- share-lib.php
- open.php
- public-file.php
- files.php
- upload.php
- teacher-login.php
- teacher-uploads.php
- teacher-delete.php
资源与数据
- school-badge.png
- metadata.json
- /Users/jarod/Sites/share/data/uploads
- /Users/jarod/Sites/scratch/static/local-assets
十、今天最有代表性的三个经验
1. “能显示”不等于“能导入”
角色库、背景库最开始就是这样:
缩略图看着已经正常,但点击后并不能真正加入编辑器。
前端里很多问题不是“有没有显示”,而是“完整的数据链有没有通”。
只修视觉层,往往会留下更难追的假象。
2. 状态机打架比单点错误更难查
上传作品打开失败,不是某个接口直接 500,也不是文件坏了,而是两套“项目加载流程”在同时响应同一个状态。
这种问题的难点在于:每一层单看都像“差不多是对的”,但放在一起就会互相抵消。
3. 尽量走原生路径,少走“看起来聪明”的兼容路径
角色库导入最终选择打包成 .sprite3 再交给 vm.addSprite(),本质上就是回到 Scratch 原生能力,而不是继续维护一个自定义对象注入方案。
这类选择通常更稳、更容易维护,也更接近未来可持续的代码形态。
十一、当前结果
到今天为止,这套系统已经基本具备完整可用形态:
- 本地 Scratch 编辑器可访问
- 角色库、背景库、本地资源库基本可用
share页面支持学生上传与老师管理.sb/.sb2/.sb3可直接从作品列表点开进入编辑器- 顶部可直接跳转到作品页
- 老师后台密码已更新为
ghtxx.cn
从“能编译出来”到“校园环境里能顺手使用”,中间其实差了很多看不见的小修补。
今天这篇博客,主要就是把这些“看不见但重要”的修补过程留下来。
后续可继续做的方向
如果后面继续完善,我觉得优先级可以是:
- 给
share增加项目缩略图或封面预览 - 给老师后台增加批量下载与批量归档
- 把作品列表按班级做分组视图
- 增加项目打开失败时更友好的诊断页
- 进一步清理
scratch中仍然残留但不影响运行的旧兼容代码
记录时间:2026年4月23日
记录位置:/Users/jarod/Sites/briefings/2026-04-23.md
2026-05-15 后续维护
5 月 15 日继续做了一轮面向真实课堂使用的收尾维护,重点包括:
- 修复声音库缩略图错误,避免把
.wav/.mp3当图片渲染。 - 增加 tts.php 和 tts-test.php,让局域网电脑通过服务器代理使用文字朗读。
- 让
tts.php显式识别 Shadowrocket 系统代理127.0.0.1:1082,并缓存语音合成结果。 - 修正文字朗读语言代码,例如
zh-cn/zh-tw统一转为cmn-CN。 - 修正 Scratch 扩展真实请求路径,避免编辑器访问
/scratch/tts.php/synth?...导致404。 - 修复随机角色、随机背景、随机造型按钮,让它们复用已经稳定的本地素材导入流程。
完整记录见: