审计修复:后端(安全 / 数据一致性 / 性能)

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 再用应用侧读查询真打一遍,
迁移块排到依赖表之前这种错只有新库会炸,老库毫无症状。
This commit is contained in:
tamakyi
2026-09-24 20:37:16 +08:00
parent 78865fc736
commit b4c2a8452f
70 changed files with 5417 additions and 360 deletions
+17 -8
View File
@@ -16,6 +16,7 @@ import (
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
"rond-with-you/internal/fog"
@@ -26,22 +27,27 @@ const maxFogUploadBytes = 64 << 20 // .fwss 快照一般几 MB,放宽到 64MB
// 迷雾是站点级数据(与公开页读数据不区分归属的约定一致),
// 内存里只持一份索引,首次访问时从库里加载。
//
// 指针用 atomic 而不是普通变量:清空迷雾(fogResetIndex)会在请求 goroutine 里换掉它,
// 而瓦片/导出/统计同时在读 —— 直接赋值就是一个真的 data race。
var (
fogOnce sync.Once
fogIndexV *fog.Index
fogIndexV atomic.Pointer[fog.Index]
)
func (s *Server) fogIndex() (*fog.Index, error) {
fogOnce.Do(func() {
fogIndexV = fog.NewIndex()
if err := fogIndexV.LoadAll(s.db); err != nil {
fogIndexV = nil
ix := fog.NewIndex()
// 加载完才发布:中途的实例不能被别的 goroutine 看到(这也是Once的意义)
if err := ix.LoadAll(s.db); err == nil {
fogIndexV.Store(ix)
}
})
if fogIndexV == nil {
ix := fogIndexV.Load()
if ix == nil {
return nil, fmt.Errorf("迷雾数据加载失败")
}
return fogIndexV, nil
return ix, nil
}
// handleFogTile 渲染迷雾透明瓦片。迷雾网格是 WGS-84;当前激活底图若是
@@ -441,8 +447,11 @@ func (s *Server) fogCoverage(ctx context.Context, datasetID int64, regions []str
// fogResetIndex 丢弃内存里的迷雾索引(清空迷雾后调用),
// 下次访问时按空库重建。
//
// 被清掉的那个索引对象可能还有请求正拿着用(Import/View/All 都走它自己的锁),
// 这里只换指针、不动旧对象,那些请求会安全地跑完。
func fogResetIndex() {
fogIndexV = fog.NewIndex()
fogIndexV.Store(fog.NewIndex())
}
// adminFogClear 清空全部迷雾数据。迷雾是站点级数据,一次清空影响所有展示。
@@ -483,7 +492,7 @@ func (s *Server) adminFogExport(w http.ResponseWriter, r *http.Request) {
_, cells := ix.Stats()
w.Header().Set("Content-Type", "application/octet-stream")
w.Header().Set("Content-Disposition",
fmt.Sprintf(`attachment; filename="fog-export-%s.fwss"`, time.Now().Format("20060102-150405")))
downloadDisposition("fog-export-"+time.Now().Format("20060102-150405")+".fwss"))
w.Header().Set("X-Fog-Cells", strconv.Itoa(cells))
w.Write(out)
}