Commit Graph
9 Commits
Author SHA1 Message Date
tamakyi 7ab33227a5 专题显示区改成在地图上点选(后台编辑器)
用户诉求的后半:不该让他手填经纬度和半径,应该在地图上圈。三个手填输入框
换成地图 + 圆列表:点地图加圆、拖圆心移动、拖圆边改半径、列表里可改半径/
定位/删除、可一次清空;改动立刻写进隐藏字段 areas(JSON) 与 crs,随专题表单
一起提交。

- 复用 /admin/fog-mask 那套约定:/api/map-config 取底图、底图坐标系随表单带上、
  后端换算成 GCJ-02。不另起炉灶。
- areas 的初值**渲染进隐藏字段**(不是只塞给 JS):脚本没加载上时提交的仍是原值,
  否则一次保存就把已有显示区静默清空。
- 地图初始视野取生效数据集的地点包围盒(placesBounds),一上来就落在自己的地界上,
  而不是全国视野。
- 上限(20 个 / 0.05~2000 km)前端也校验一份,并**禁用提交**;两边数字用守卫比对,
  免得出现「前端放行、后端拒绝,用户不知道改哪里」。

实测抓到并修掉的 bug:/admin/topics 没设 p.HasMap ⇒ 布局不引入 Leaflet,
表现是脚本加载了、rondTopicAreas 也在,就是 window.L 永远 undefined,init 静默
返回、控制台不报错、页面像「什么都没发生」。已加守卫钉住这一点。

其余:
- topic-areas.js 只暴露纯函数(校验/序列化/增删改的不可变性/几何换算)供 node 单测,
  另加一个最小测试钩子 api._map 供无头验证(同 window.rondMap._state 惯例)。
- CSS 用定宽容器装半径输入框:站点全局 input 规则是 width:100% 且带 10 个 :not()
  (特异性 (0,11,1)),直接给 input 写宽度压不过。
- gofmt:文档注释里的缩进列表按 Go 1.19 的规则重排;顺带把工作区的 CRLF 归一到 LF
  (.gitattributes 早就声明 eol=lf,blob 本来也是 LF,所以 git diff 无额外噪声)。

验证:
- topic-areas.test.js 44 条(含「增删改返回新数组不原地改」、半径换算自洽性)。
- 三个新守卫:两边上限一致、模板契约(旧输入框必须没了 + 挂点齐全 + 初值来源)、
  topicsAdminPage 必须设 HasMap。
- 无头端到端:两击成两圆 → 改半径 7.5 → 删第 1 个 → 越界半径被拦且禁用提交 →
  提交 200 且落库 areas=[{lat:24.72,lon:108.1,radiusKm:7.5}](废弃列镜像 7.5);全程无 JS 报错。
2026-10-12 17:35:00 +08:00
tamakyi 44763dfc3f 专题的多圆显示区:前台按并集裁剪迷雾
上一批把显示区改成了「一组圆」,这一批让前台真正认多个圆。

- clip-path 从 circle() 换成 path():circle() 只表达得了一个圆。每个圆一段
  圆弧子路径、**同绕向**(sweep=0),nonzero 填充规则下多段自然取并集,
  重叠处不会互相挖洞。
- 拼路径的活单独放 fog-clip.js(rond-map.js 是个大 IIFE,函数取不出来),
  这样能直接跑 node 单测;layout 里必须排在 rond-map.js 之前加载。
- 顺带清掉已经死掉的 fitCircle:多圆之后「圆心 + 半径」表达不了,留着就是
  两套并行的贴合口径。fitParams 现在只给 fitBBox(单圆时外接矩形正是那个
  圆的外接正方形,视野等价)。
- 守卫改名 TestTopicFogIsClippedToAreas,钉住两端(模板发 fogAreas ↔ 脚本认
  fogAreas)与两处顺序(裁剪注册在 applyFit 之后、fog-clip.js 在 rond-map.js
  之前);fogAreas 必须**无条件**下发(空数组 = 不限区域),不要包在条件里。
- verify.sh 挂进前端单测:以前只靠人记得手动跑 = 等于没有。找不到 node 直接
  失败而不是跳过(跳过会让验收看着全绿、实际一行前端脚本都没验)。

验证(临时两圆专题,SQL 造的,已删):
- fog-clip.test.js 15 条:把生成的 path 解析回圆弧再验几何,含绕向一致性与
  半径 0/负数的夹取。
- 端到端像素验证:自校准出「CSS 容器坐标 → 图像像素」(scale≈2、offset≈19.2,
  正是 site_cdp 的 --clip 外扩 10px×scale,别再手算);三张掩码图证明并集语义
  —— A 独有点只有 A 亮、B 独有点只有 B 亮、**重叠中心两圆都亮且并集也亮
  (无洞)**,整体集合差异 0.013%。
- 迷雾掩码:86,776 个像素,按两圆并集的解析式判 **圆外 0 个**;两个圆内分别
  有 52,609 / 46,757 个迷雾像素 ⇒ 第二个圆真的被认了,不是只看第一个。
2026-10-12 16:44:55 +08:00
tamakyi 1bb35dbbc9 专题显示区改为一组圆(后端与查询层)
把专题的「一个圆」换成「一组圆」(areas jsonb 数组):落在任一圆内即算在
区域内(SQL 里的 OR)。这一批只动后端与查询层,前台渲染与后台地图编辑器
分别跟进;后台表单暂时沿用原来的三个坐标输入框,保存时合成一个圆写进
areas,所以现有交互不受影响。

- topics.areas JSONB + 存量回填:老的单圆搬进去,之后 areas 是唯一权威。
  废弃的 center_lat/center_lon/radius_km 保留(写入时镜像第一个圆),
  一是回滚旧二进制时还能退化显示成第一个圆,二是回填的输入。
- Topic.AreaList() 归一:Areas 为空时退回老的三列,未回填的老行与直接构造
  Topic 的调用方都继续工作。
- topicAreaCond 改成 N 个圆的 OR;每个圆占 3 个占位符,编号纪律照旧
  (写错就是「专题内容全变 0」那种难查的事故)。
- fitParams 改用所有圆的并集外接矩形:多圆之后「圆心+半径」表达不了,
  统一走 fitBBox;单圆时外接矩形就是这个圆的外接正方形,与原路径等价。
- 坐标系:编辑器提交的圆心若是 WGS-84 底图上点的,存前换算成 GCJ-02
  (地点过滤要拿它跟 places 的 GCJ-02 坐标比距离)。

测试:
- 表单解析(两个入口同一套校验)、wgs84 换算、半径为 0/空 = 不限区域、
  坏 JSON/越界必须报错、上限 20 个。
- 占位符纪律:本段引用的 $n 必须恰好是 [prefix+1, len(args)] 连续区间,
  最大值必须等于参数个数。
- 多圆并集语义(DB 门控):两圆专题的地点集合必须恰好等于两个单圆专题的
  并集 —— 定义性不变量,变异验证过(只认第一个圆 ⇒ 两个用例同时红)。
2026-10-12 16:29:46 +08:00
tamakyi b19f27ab69 日期框补上日历弹层:flatpickr 改为无条件加载
用户实测:筛选区日期框换成文本框后手机上没了日历弹层、不好操作;多 18KB gzip 可以接受。

- layout.html 里 flatpickr(js + css + l10n)与 datetime-input.js 都改为**无条件加载**,
  不再用 {{if .Flatpickr}} 按页开关 —— 「按页开开关」正是 2026-10-09 日期框 bug 的根源
  (只给 type=datetime-local 挂了控件,后来加的 type=date 没人管)。
- 顺手删掉 `Page.Flatpickr` 字段与三处 `p.Flatpickr = true`(开关没了就不可能再漏设)。
- 守卫 TestDateInputsGoThroughAttachAll 相应改成:钉住「layout 无条件引入 flatpickr +
  datetime-input + attachAll」、并禁止再出现 {{if .Flatpickr}}。

实测(展开筛选面板后):390/320 下 /stats /map /places 的日历都在最上层(z-index 99999)、
完全在视口内、elementFromPoint 命中日历本身;打字 20260606 → 2026-06-06 不变。

回归:./scripts/verify.sh PASS=349 FAIL=0 SKIP=0;JS 单测 45/45;320/390/1440 溢出与改前一致。
2026-10-09 19:09:42 +08:00
tamakyi 770e752a7a 图片恢复支持对象存储(R2);专题也改成归人不归数据集
R2 上的图怎么救:库里的行没了就只剩桶里有那些键,不列桶无从得知它们叫什么。
给 internal/media 加了 ListObjects(ListObjectsV2 + SigV4 + 自动翻页 + XML 解析):
- 路径式访问、请求只到桶;查询串必须**先百分号编码再签名**(canonicalQuery 按原样取
  RawQuery,两边得是同一份字节,prefix 里的 / 要写成 %2F)。
- ⚠️ R2 的 API Token 默认只给对象读写,没有 s3:ListBucket 就 403。列桶失败**不让整个
  恢复失败**:本地那半照救,原因进 Warnings、页面用红色提示条显示(否则站长会以为恢复完了)。
- 候选对象 = 本地目录 ∪ 桶里 .webp(同键以本地为准);行的 storage 按「对象实际在哪」写,
  bytes 用桶里报的 Size。另加一条 warning:挂回了却没配 s3_public_base ⇒ 行有了页面空白,
  这比「没恢复」更难懂。

专题(用户拍板「一并做」):topics.dataset_id 同样带 CASCADE,删数据集带走专题,而专题
只存在这张表里、没有恢复来源。外键摘掉,所有查询 dataset_id → user_id(loadTopics、
topicPage、保存/删除/开关、pageBase.HasTopics);**内容仍照当前生效数据集算**
(topicContent 不动)——变的是「这份定义归谁」。Dataset 补 UserID 供前台按属主定位
用户级数据;管理专题不再要求有生效数据集。

导出链路未改动:place_images / topics / Dataset.UserID 都不参与 .rondbackup 导出,
全量验收(含导出保真、ZRAW 夹取、编号对齐那批)PASS=331 FAIL=0 SKIP=0。
2026-09-30 23:31:17 +08:00
tamakyi 4597047989 口径:已并入 / 子段的到访不再计入时间线与统计(导出侧一个字节不动)
真机里两行到访可能「不独立成立」:ZMERGEDTO 指向它被并入的那条、ZPARENT 说明它只是某个
父段的一部分。app 对这两种行既不显示也不计数,本站以前当成普通到访 ⇒ 时间线上出现
「同一时刻同一地点两条」、日停留重复计入(实测数据集 9 有 64 条,26 条到达时间与原件相同)。
这些行**不能删**——导出要靠它们认这次合并发生过,所以判据只立一份 stats.SoloVisitSQL,
在读的一侧收口:visitsWhere / VisitsByPlace / Bookmarked / placeCols 子查询、首次到达城市、
地图极值点、专题(详情 + 列表批量)、三处聚合的 visit_count 与 dwell_minutes,
外加 schema.sql 一段幂等回填把存量库拉正。

天气与位移**不收口**(那几小时、那段路真实发生过);导出侧一个字节不动(rond 只读
places.last_visit_at,保持全量,压根不读 visit_count / dwell_minutes)。编辑页照样列出
这些行,但打上「已并入 #562 / 子段 #374」,免得以为数据丢了。

顺带修两个自测抓出来的问题:
- ingest 的地点聚合回填排在 insertEntityRaw 之前 ⇒ 判据读到空留档 ⇒ 每行都算独立到访
  (整整多算一个「已并入」的量)。
- 收口后 last_visit_at 保持全量,会凑出「0 次却有最近到访」(数据集 9 有 4 个地点),
  展示侧按可见到访数兜住。

验收:304 PASS / 0 FAIL / 0 SKIP(新增 3 个用例:标记后导出仍带该行、存量回填拉正、
判据只出现在读侧)。
2026-09-29 21:15:25 +08:00
tamakyi b4c2a8452f 审计修复:后端(安全 / 数据一致性 / 性能)
audit-2026-09-21.md 里的后端条目落地。每条都配了回归守卫,并逐条做过变异验证
(注入原 bug → 确认按正确理由变红 → 还原 → 变绿)。

安全
- CSRF 只加同源 Origin/Referer 守卫(csrf.go),不动模板
- 会话 cookie 的 Secure 按请求判断;两步验证两闸(loginGuard + 验证码一次性)
- 跳转收口到 safeInternalPath;clientIP 只认 XFF 最后一段
- GPX 按属主圈定且查 RowsAffected;下载名走 downloadDisposition
- 上传口统一 MaxBytesReader;落盘后的失败路径清掉半成品

数据一致性
- 区域判定两端各过一遍再 AND(apiTrack 与 movementsWhere 对齐)
- Filter.Verdict 全域一致(下推到 visitsWhere);聚合刷新必须返回 error
- 附属数据改挂坐标键,Go 与 SQL 两套表达式逐位一致
- 脏备份的伪时间/越界坐标不再进领域模型;rows 循环统一 drainRows
- 合并与导出:writeRawRow 上抛错误、悬空引用清理、原生列补全

性能
- placeCols 相关子查询带 dataset_id 前导列;findSourceArchive 先按大小筛
- 批量勾选改 ANY($3::bigint[]);fillupCache 加上限;同一查询不再查两遍

另:新增「全新库」冒烟测试 —— 空库跑完 Migrate 再用应用侧读查询真打一遍,
迁移块排到依赖表之前这种错只有新库会炸,老库毫无症状。
2026-09-24 20:37:16 +08:00
tamakyi ad2531c113 修复图片上传分类无法新增、潜在movementWindow容差问题、404页面未生效问题 2026-09-18 17:06:22 +08:00
tamakyi b327bc1230 first release 2026-09-18 13:05:49 +08:00