启动模板自检 + 站长导航放行 + 统计脚本关口收紧
排查:/admin/settings 渲染到一半就断
- 现象:页面在「统计脚本」那张卡片处截断,没有保存按钮、没有 </form></html>,而 HTTP 仍是 200。
- 根因:跑的是旧二进制(改动前编译),磁盘上的模板却是新的。debug=true 下 pageSet()
每次渲染都从磁盘重解析模板,于是新 settings.html 引用了旧 setting.Settings 里没有的
TrackEnabled —— 执行期报错,而 render 只把错误写进日志,客户端只看到半张页面。
- 判据:strings <二进制> | grep -c cleanTrackScript(旧 0 / 新 2)。别用 strings | grep
TrackScript —— config 里本来就有这个名字,旧二进制也命中,是个假信号。
启动自检(防复发)
- template_smoke.go:pageProbe 给 24 个页面各配一份最小可用数据,New() 里全部渲染一遍,
任一页失败就拒绝启动。pageProbe 与 pageFiles 双向校验,新加页面必须补探针,
漏配按失败处理(不是省事)。
- smokeOne 用 recover 兜住方法里的 panic,报「哪一页坏了」而不是崩栈。
- 实测:往 settings.html 插一个不存在的字段 → 启动即报
「模板自检未通过…admin/settings: can't evaluate field …」,退出码 1。
- 门禁 template_smoke_test.go:一条跑全量自检,一条变异验证(探针换成字段更少的类型
必须报错且点名该页)。
站长导航被页面开关误伤
- guardPage 只拦访客(站长始终能访问全部公开页),但导航写的是 {{if .Settings.HasPage "x"}},
没有 IsAdmin 分支 ⇒ 页面开关一关,站长自己也只剩「主页」一个入口 —— 能访问却点不到。
改成 {{if or .IsAdmin (.Settings.HasPage "x")}}(map/places/timeline/stats/report 五处)。
- 门禁 nav_visibility_test.go:三向不变量(站长仍有全部入口 / 访客一个都没有 /
全开放时访客也看得到),做过变异验证。
- 访客侧「只有主页」是按后台配置应有的状态,没有动数据。
统计脚本关口收紧(cleanTrackScript)
- src= 只在 <script …> 开标签内部找。原来对整段做 strings.Index,脚本体里一句
`img.src=u` 就被命中、整段被判危险而清空 —— 页面照样提示「设置已保存」,站长看不出没挂上。
- 开闭标签从「只拒 shut > open」改成严格配平:末尾未闭合的 <script> 会把页面收尾整段吞进脚本。
- 不再静默:签名改成 (script, warn),说明码经 ?warn= 回显成人话;判为危险时整段不采纳、
库里原脚本与开关都不动(把站长手上好用的脚本一并清掉更糟)。
- 门禁 TestCleanTrackScript 重写:逐条边界 + 定义性不变量(采纳了非空结果时,
里面开闭标签数必定相等)+ TestTrackWarnMessage(未识别码不得回显)。变异验证做过。
- 文案订正:settings.html 的「conf/app.ini 的 track_script 会作为这里的默认值」有歧义 ——
它不回填文本框,而是「这个框留空时、注入那一刻才回落到 ini」。
其余
- bin/ 目录删除(15 个 exe / 412MB),二进制只留仓库根 rond.exe;
技能 rond-web-verify 与 memory details 十八里 bin/rond.exe 的写法同步改掉。
- 验收:./scripts/verify.sh 全绿且 0 skip(PASS=346 FAIL=0 SKIP=0)。
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
# 2026-10-09
|
||||
|
||||
## 排查:/admin/settings 报错(旧二进制 + 新模板)
|
||||
|
||||
**现象**:「统计脚本做成后台开关」那次提交(4015ddf)之后,访问 /admin/settings 报错。
|
||||
|
||||
**根因(已实测复现)**:跑的是**旧二进制**(根目录 `rond.exe`,10-08 18:06 编译,早于 18:27 的提交),
|
||||
磁盘上的模板却是新的。`debug=true` 下 `pageSet()` 每次渲染都从磁盘重解析模板,
|
||||
于是新 `settings.html:148` 引用了旧 `setting.Settings` 结构体里没有的 `TrackEnabled` → 执行期报错:
|
||||
|
||||
```
|
||||
渲染 admin/settings 失败: template: settings.html:148:85: executing "content"
|
||||
at <.Settings.TrackEnabled>: can't evaluate field TrackEnabled in type setting.Settings
|
||||
```
|
||||
|
||||
`render` 是流式直写 ⇒ header 已发(HTTP 200)但表单在「统计脚本」卡片处截断,
|
||||
没保存按钮、没 `</form></html>`。这就是用户看到的「报错」。
|
||||
|
||||
**判据**:`strings <二进制> | grep -c cleanTrackScript` —— 旧根二进制 0,新编译的 2。
|
||||
(注意别用 `strings | grep TrackScript` 判定,config 里本来就有这个名字,旧二进制也命中。)
|
||||
|
||||
**修复**:`go build -o rond.exe .`(+ `bin/rond.exe`),重启。旧二进制备份到
|
||||
`bin/rond.exe.old-20261008-stale`。修后 /admin/settings 完整(19642B,含 `</html>`/保存按钮)。
|
||||
|
||||
**教训**:Go 结构与模板字段是成对改的,`debug=true` 的热加载只覆盖模板 —— 改 Go 必须重编译重启,
|
||||
否则就是「新模板 + 旧结构 = 执行期报错」。想根治可在启动时把所有页面各渲染一遍做冒烟(现在没有)。
|
||||
|
||||
## 其他发现(未改代码)
|
||||
|
||||
1. **P1 待确认**:`settings` 行 `pages=null`(写入时间 2026-10-08 18:14:05)⇒ 访客侧
|
||||
`/map /places /timeline /stats /report` 全部 404、导航只剩 1 个链接。代码语义是
|
||||
「全不选 = 访客只能看主页」,属合法状态,像是测 404 分档时留下的。**没动数据,等确认**。
|
||||
2. **P2**:`cleanTrackScript` 的 `src=` 检查是**全文 `strings.Index`**,会误伤脚本体内的
|
||||
`src=`。实测:`<script>var i=new Image();i.src=u;</script>` → 返回 `""`(整段被静默清空),
|
||||
而页面仍提示「设置已保存」⇒ 静默失效。建议只在标签属性里查 src,或把「被清空」回传前端显式提示。
|
||||
3. **P2**:开闭标签只拒 `shut > open`;末尾未闭合 `<script>`(`open > shut`)会吞掉后面内容。
|
||||
实测 `<script>x</script><script>y` 与 `<script>y` 都被放行。建议改成要求 `open == shut`。
|
||||
4. **运维**:根 `rond.exe` 与 `bin/rond.exe` 两份二进制容易走偏(这次根目录那份就是旧的)。
|
||||
考虑只保留 `bin/`,或在启动日志打印构建时间便于比对。
|
||||
|
||||
**回归**:`go vet` 干净、`gofmt -l` 空、JS 单测 34/34、`go test ./...` 13 包全 ok 0 FAIL。
|
||||
(注:`internal/web` 里 110 例是 DB 门控的,不开 `ROND_TEST_CONF`/`ROND_SRC` 会 skip,不算真跑过。)
|
||||
|
||||
---
|
||||
|
||||
## 用户确认后的收尾(四件)
|
||||
|
||||
用户反馈:「管理员访问也只剩主页,这好像不对」+ P2 按建议修 + 二进制只留根目录 + 防复发一并做。
|
||||
|
||||
### 1. P1 更正:不是配置问题,是导航缺 IsAdmin 分支
|
||||
|
||||
上面第 1 条判断得不完整。实测:站长访问 /map /places /timeline /stats /report **全是 200**
|
||||
(`guardPage` 只拦访客),但导航里只有「主页」⇒ `layout.html` 的页面显隐写的是
|
||||
`{{if .Settings.HasPage "x"}}`,**没有 IsAdmin 分支**,与 `guardPage` 口径不一致。
|
||||
改成 `{{if or .IsAdmin (.Settings.HasPage "x")}}` 五处。门禁 `nav_visibility_test.go`
|
||||
(三向不变量 + 变异验证)。**访客侧的 pages=null 没动**(仍是「只对访客关」的配置状态)。
|
||||
|
||||
### 2. P2 修复:`cleanTrackScript` 重写
|
||||
|
||||
- `src=` 只在 `<script …>` **开标签内部**找(原来对整段 `strings.Index`,`i.src=u` 被误伤清空)。
|
||||
- 开闭标签从「只拒 `shut > open`」改成**严格配平**(末尾未闭合的开标签原来会被放行)。
|
||||
- 签名改 `(script, warn string)`,说明码经 `?warn=` 回显成人话;判危险时**整段不采纳、
|
||||
库里原脚本与开关不动**。
|
||||
- 实测(测试实例 8081 / `rond_fog_test`,用完已还原):`i.src=u` 现在原样落库;
|
||||
不配平 → `?warn=unbalanced` 且库值不变;`javascript:` → `?warn=badsrc` 且库值不变;
|
||||
5000 字符 → `?warn=truncated` 且落库 4096。注入范围精确复核:只在公开页(/、/map),
|
||||
后台与登录页 0。
|
||||
|
||||
### 3. `bin/` 删除
|
||||
|
||||
15 个 exe、412MB 全删;只保留根 `rond.exe`。技能 `rond-web-verify` 与 details 十八的
|
||||
`bin/rond.exe` 写法已同步改掉。
|
||||
|
||||
### 4. 防复发:启动自检 `smokeRender`
|
||||
|
||||
`template_smoke.go`:`pageProbe`(24 个页面各一份最小可用数据,键与 `pageFiles` 双向校验)
|
||||
+ `New()` 里全量渲染一遍,失败即 `log.Fatalf` 拒启。实测:往 settings.html 插一个不存在的
|
||||
字段 → 启动报 `模板自检未通过…admin/settings: can't evaluate field …`,退出码 1。
|
||||
门禁 `template_smoke_test.go`(含变异验证:把探针换成字段更少的类型必须报错)。
|
||||
|
||||
### 5. 用户裁决 + 文案订正
|
||||
|
||||
- **访客侧 `pages=null` 不动**:用户明确说「访客那一侧是根据我在后台配置的显示范围公开的,
|
||||
倒不如说原来默认就是这样才对」⇒ 那是**预期配置**,不是故障。已把这个结论写进
|
||||
details 四十一,免得后续会话又去「修」数据。
|
||||
- **settings.html 的统计脚本文案订正**(P3):原文两处不准 ——
|
||||
①「多一个 `</script>` 会被判为粘贴错误」漏了「少一个也会拒」(判据已改成严格配平);
|
||||
②「`conf/app.ini` 的 `track_script` 会作为这里的默认值」有歧义:它**不会回填文本框**,
|
||||
而是「这个框留空时,注入时回落到 ini 的值」。已改成准确说法。
|
||||
实测热加载生效(debug=true,无需重编),页面 19996B、含 `</html>`,旧文案 0 命中。
|
||||
- 验收:官方 `./scripts/verify.sh` → **PASS=346 FAIL=0 SKIP=0**(改动前那次全量;
|
||||
之后只动了这一个模板文案,重跑了全部模板门禁与 `TestSmokeRender*`,全绿)。
|
||||
|
||||
@@ -110,7 +110,7 @@ zip 包,**条目内容本身是 zlib 流**;`Model/*/` = 头 32768B + 每块
|
||||
|
||||
## 十八、重启与操作纪律
|
||||
|
||||
- **重编重启**:`go build -o bin/rond.exe .` → 停掉占 8080 的进程 → `./bin/rond.exe -c conf/app.ini`。`taskkill //F` 的双斜杠参数会被 Git Bash 吃掉,改用 PowerShell 按端口找 PID + `Stop-Process`。
|
||||
- **重编重启**:`go build -o rond.exe .`(**只构建到仓库根,`bin/` 已废弃、2026-10-09 删除**)→ 停掉占 8080 的进程 → `./rond.exe -c conf/app.ini`。`taskkill //F` 的双斜杠参数会被 Git Bash 吃掉,改用 PowerShell 按端口找 PID + `Stop-Process`。
|
||||
- 生产 `life.shiroko.one` **只走 IPv4**(curl 必须 `-4`);会话 cookie `rond_session=uid|token`(HMAC);HTTP 303 **不能跟随**(Location 里带 `ok=`/`err=`)。`/tmpfog/` 不是运行必需,`tmpfog/_ck.txt` 是**死票**(实打接口被 302)⇒ 不必轮换 `secret_key`。
|
||||
- **`gofmt -l` 会报假警**:仓库存 LF、`core.autocrlf=true` 把工作区换成 CRLF(已加 `.gitattributes` 钉住);**用 python `open(p,'w')` 回写 Go/CSS 会把行尾变 CRLF**,要二进制模式读写。
|
||||
- **同一文件绝不并行改**:多条 Edit 一起发给同一文件时,各自「读整份-替换-写整份」会互相覆盖——工具**报 success 但磁盘上没落**(实测一次发 3 条给 `map.html`,`map-count` 那条丢了)。**一个文件串行改,改完 `grep` 复核关键标记,再编译。**
|
||||
@@ -879,14 +879,69 @@ Esc 关闭、焦点自动进输入框、其余部分自动 inert。旧浏览器
|
||||
`settingsSubmit()` 的表单读取。`Load()` 里布尔项一律用 `present[key]` 判:
|
||||
历史配置没有这个键时若直接套零值,语义会悄悄反转。
|
||||
|
||||
### `cleanTrackScript` 的判据(这里踩过一次)
|
||||
### `cleanTrackScript` 的判据(这里踩过两次,2026-10-09 重写)
|
||||
|
||||
⚠️ **不能用「出现 `</script` 就拒」当规则** —— 百度统计、Umami 贴进来的就是完整
|
||||
HTML,一行里必然带 `</script>`,一拒就把正常用法挡死了(我第一版就是这么写的,
|
||||
被自己的测试当场抓住)。真正的注入面只有一种:**未闭合的 `<script>` 里出现
|
||||
`</script>` 会提前闭合**,后面内容当正文渲染。判据改成**开闭标签数配平**
|
||||
(`shut > open` 才拒),配平则整段原样放行。
|
||||
另挡 `src=` 的 `javascript:` / `data:` 伪协议(只放行 http/https 与 `/` 开头),
|
||||
超 4096 截断。
|
||||
门禁 `TestCleanTrackScript` 除了逐条边界,还钉了**定义性不变量**:
|
||||
任何输入都不得产出「含 `</script` 的非空结果」。
|
||||
被自己的测试当场抓住)。
|
||||
|
||||
现行判据两条:
|
||||
1. **开闭 `<script>` 标签必须严格配平(`open != shut` 就拒)**。少一个 `</script>`
|
||||
会提前闭合、后面内容当正文渲染;多一个 `<script>` 会把页面剩下的收尾整段吞进脚本。
|
||||
⚠️ 只拒 `shut > open` 是不够的(初版就是),末尾未闭合的开标签会被放行 ——
|
||||
`<script>x</script><script>y` 实测通过,已改成严格配平。
|
||||
2. **`src=` 只在 `<script …>` 开标签内部找**,只放行 `http(s)://` 与 `/` 开头的相对路径。
|
||||
⚠️ 初版对**整段**做 `strings.Index(low, "src=")`,脚本体里一句 `img.src=u` 就被命中、
|
||||
整段被判危险而清空 —— 而页面照样提示「设置已保存」,站长完全看不出脚本没挂上。
|
||||
实测:`<script>var i=new Image();i.src=u;</script>` 旧版返回 `""`,现在原样放行。
|
||||
超 4096 截断。
|
||||
|
||||
**收尾要显式**:函数签名改成 `(script, warn string)`,说明码
|
||||
(`truncated` / `unbalanced` / `badsrc`)经 `?warn=` 跳到设置页、由 `trackWarnMessage`
|
||||
翻成人话显示。用**固定码**而不是回显查询串原文(避免反射内容)。
|
||||
判为危险时**整段不采纳、库里原脚本与开关都不动**(把站长手上好用的脚本一并清掉更糟)。
|
||||
|
||||
门禁 `TestCleanTrackScript` 逐条钉边界 + **定义性不变量**:
|
||||
采纳了非空结果时,里面的开闭标签数必定相等(不是「不得含 `</script`」——整段贴进来的
|
||||
本来就带闭合标签)。
|
||||
|
||||
## 四十一、2026-10-09 三处修复:导航口径 / 启动自检 / 二进制只留根目录
|
||||
|
||||
### 1. 导航的页面显隐**只管访客**(`layout.html`)
|
||||
|
||||
`guardPage`(`server.go`)拦的只是访客:`userFrom(ctx)==nil && !sets.HasPage(nav)` → 404;
|
||||
站长始终能访问全部公开页。但导航写的是 `{{if .Settings.HasPage "map"}}`,**没有 IsAdmin 分支**
|
||||
⇒ 页面开关一关,站长自己也只剩「主页」一个入口 —— 能访问却点不到。
|
||||
已改成 `{{if or .IsAdmin (.Settings.HasPage "x")}}`(map/places/timeline/stats/report 五处)。
|
||||
门禁 `nav_visibility_test.go`:直接执行 `nav` 模板,钉「关掉开关后站长仍有 5 个入口、
|
||||
访客一个都没有、全开放时访客也看得到」三向不变量(做过变异验证)。
|
||||
|
||||
⚠️ **访客侧「只有主页」是配置出来的预期状态,别去"修"数据**:库里 `settings.pages = null`
|
||||
(= 站长把公开页面全关了),访客 `/map /places /timeline /stats /report` 一律 404。
|
||||
2026-10-09 用户明确确认:**访客能看哪些页就是按后台配的范围公开,这样才对**。
|
||||
要改就改后台的「公开页面」开关,不要直接动 `settings` 行。
|
||||
|
||||
### 2. 启动自检 `smokeRender`(`template_smoke.go`)
|
||||
|
||||
⚠️ **「模板能解析」不等于「渲染得完」**:字段不存在、nil 解引用都只在**执行期**暴露,
|
||||
而 `render` 只把错误写进日志 ⇒ 线上是「HTTP 200 + 渲染到一半的半张页面」。
|
||||
2026-10-09 的 /admin/settings 截断就是这么坏的(提交里同时改了 `setting.Settings` 和
|
||||
`settings.html`,跑的是改动前编译的二进制,debug 下模板走磁盘热加载 → 新模板 + 旧结构体)。
|
||||
|
||||
做法:`pageProbe`(每个页面一份「最小可用数据」)+ `smokeRender` 在 `New()` 里把
|
||||
**所有页面各渲染一遍**,任一失败就 `New` 返回 error → main `log.Fatalf` **拒绝启动**。
|
||||
- `pageProbe` 的键必须与 `pageFiles` **双向一致**:缺项按失败处理(新加页面必须补探针)。
|
||||
- 探针值只求「类型对、不炸」——页面本来就得能渲染空数据。
|
||||
- `smokeOne` 里 `recover()` 兜住方法里的 panic,报出「哪一页坏了」而不是崩栈。
|
||||
- 门禁 `template_smoke_test.go`:一个跑全量自检;一个**变异验证**(把 `admin/settings`
|
||||
的探针换成字段更少的 `Page`,必须报错且点名该页)。
|
||||
- 实测:往 `settings.html` 插一个不存在的字段 → 启动即报
|
||||
`初始化服务失败: 模板自检未通过…admin/settings: …can't evaluate field …`,退出码 1。
|
||||
|
||||
### 3. 二进制只留在仓库根(`bin/` 已删,2026-10-09)
|
||||
|
||||
`go build -o rond.exe .` 一个位置。`bin/` 整套(15 个 exe、412MB)已删;
|
||||
`.gitignore` 的 `/bin` 保留作为兜底。`scripts/build-linux.sh` 照旧只产
|
||||
`dist/rond-linux-amd64`。相关技能(`rond-web-verify`)与 details 十八已同步改掉
|
||||
`bin/rond.exe` 的写法。
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
写模板时人站在站长视角下笔,「我的结论 / 你自己 / 我推荐的地点」露给访客就没人读得懂。
|
||||
照 `place.html` 投票区与 `rond-map.js` 气泡的写法。门禁 `TestVisitorPagesUseThirdPersonCopy`
|
||||
(以访客 ctx 渲染 `/`、`/stats`、`/report` 逐个断言禁用词,并确认站长那份仍说「我的」)。
|
||||
- 改 Go 必须重启;模板/CSS/JS 只在 `debug=true` 热加载(`AssetV` 启动哈希)。
|
||||
- 改 Go 必须重启;模板/CSS/JS 只在 `debug=true` 热加载(`AssetV` 启动哈希)。⚠️ **新模板+旧二进制**=「渲染到一半仍 200」;启动自检 `smokeRender` 会拒启(四十一)。
|
||||
- 拼查询串走返回 `template.URL` 的方法(`PlaceLink`/`EditPlaceLink`/`MapLink`/`EditLink`),勿手工转义。
|
||||
- 窄屏覆盖写 `app.css` **末尾那块**;宽表堆叠类名**成对** `class="tb tb-stack"`;触控 44px。`tb_stack_test.go`
|
||||
- 加表格列:`PlaceCols`/`scanPlace` **成对**、新列追加最后;CJK 挤换行只抬该表 `min-width`。
|
||||
|
||||
Reference in New Issue
Block a user