审计修复:后端(安全 / 数据一致性 / 性能)
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:
@@ -7,6 +7,7 @@ import (
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -202,23 +203,77 @@ func TestPlaceUpdateBatchActivity(t *testing.T) {
|
||||
t.Fatalf("打开导出包失败: %v", err)
|
||||
}
|
||||
defer edb.Close()
|
||||
ph := make([]string, len(pks))
|
||||
ph := make([]string, 0, len(pks))
|
||||
args := make([]any, 0, len(pks)+1)
|
||||
for i, v := range pks {
|
||||
ph[i] = "?"
|
||||
// ⚠️ 参数顺序必须与 SQL 里占位符的出现顺序一致:`FILTER (WHERE ZACTIVITY_=?)` 在
|
||||
// SELECT 列表里,它的 `?` 是整个语句的第一个,所以 actSrc 要排在所有 pks 之前。
|
||||
// 曾经写成先追加 pks、最后追加 actSrc,于是这个 `?` 绑到了 pks[0] 上,
|
||||
// 同时 IN 列表被顶掉一位、变成 pks[1:] ∪ {actSrc}:条数偶尔少 1(正是历史 flaky 的来源),
|
||||
// 而 converted 只有碰巧 pks[0] == actSrc 时才等于 246。
|
||||
args = append(args, actSrc)
|
||||
for _, v := range pks {
|
||||
ph = append(ph, "?")
|
||||
args = append(args, v)
|
||||
}
|
||||
args = append(args, actSrc)
|
||||
var inPackage, converted int
|
||||
q := `SELECT count(*), count(*) FILTER (WHERE ZACTIVITY_=?) FROM ZVISIT WHERE Z_PK IN (` +
|
||||
strings.Join(ph, ",") + `)`
|
||||
if strings.Count(q, "?") != len(args) {
|
||||
t.Fatalf("占位符 %d 个、参数 %d 个,绑定位错位了", strings.Count(q, "?"), len(args))
|
||||
}
|
||||
if err := edb.QueryRow(q, args...).Scan(&inPackage, &converted); err != nil {
|
||||
t.Fatalf("读导出包失败: %v", err)
|
||||
}
|
||||
if inPackage != len(pks) {
|
||||
t.Fatalf("导出包里只有 %d/%d 条到访", inPackage, len(pks))
|
||||
// 两种成因的修法完全不同,所以失败时直接把证据打出来:
|
||||
// ① pks 自身有重复 —— IN 列表里有 246 个值、只命中 245 个不同的行
|
||||
// ② pks 唯一但包里真缺行 —— 导出/覆盖那条路把某一行弄丢了
|
||||
missing, dup, total := diffVisitPKs(t, edb, pks)
|
||||
t.Fatalf("导出包里只有 %d/%d 条到访(包内 ZVISIT 共 %d 行);缺 %v,pks 内重复 %v",
|
||||
inPackage, len(pks), total, missing, dup)
|
||||
}
|
||||
if converted != len(pks) {
|
||||
t.Fatalf("导出包里只有 %d/%d 条到访的活动换成了 #%d(站点内部 id %d)", converted, len(pks), actSrc, actID)
|
||||
}
|
||||
}
|
||||
|
||||
// diffVisitPKs 说清「包里的到访为什么比预期少」:返回包里缺的编号、pks 里重复的编号、
|
||||
// 以及包内 ZVISIT 的总行数。总行数能立刻区分「少写了一行」和「两行撞成一个 Z_PK」。
|
||||
func diffVisitPKs(t *testing.T, edb *sql.DB, pks []int) (missing, dup []int, total int) {
|
||||
t.Helper()
|
||||
have := map[int]bool{}
|
||||
rows, err := edb.Query(`SELECT Z_PK FROM ZVISIT`)
|
||||
if err != nil {
|
||||
t.Fatalf("读导出包的到访编号失败: %v", err)
|
||||
}
|
||||
for rows.Next() {
|
||||
var pk int
|
||||
if err := rows.Scan(&pk); err != nil {
|
||||
rows.Close()
|
||||
t.Fatalf("扫导出包到访编号失败: %v", err)
|
||||
}
|
||||
have[pk] = true
|
||||
total++
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
rows.Close()
|
||||
t.Fatalf("读导出包到访编号中断: %v", err)
|
||||
}
|
||||
rows.Close()
|
||||
|
||||
want := map[int]int{}
|
||||
for _, v := range pks {
|
||||
want[v]++
|
||||
}
|
||||
for v, c := range want {
|
||||
if c > 1 {
|
||||
dup = append(dup, v)
|
||||
}
|
||||
if !have[v] {
|
||||
missing = append(missing, v)
|
||||
}
|
||||
}
|
||||
sort.Ints(missing)
|
||||
sort.Ints(dup)
|
||||
return missing, dup, total
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user