Files
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

101 lines
3.1 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package main
import (
"context"
"flag"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"rond-with-you/internal/auth"
"rond-with-you/internal/config"
"rond-with-you/internal/db"
"rond-with-you/internal/ingest"
"rond-with-you/internal/web"
)
func main() {
confPath := flag.String("c", "conf/app.ini", "配置文件路径")
flag.Parse()
cfg, err := config.Load(*confPath)
if err != nil {
log.Fatalf("加载配置失败: %v", err)
}
database, err := db.Open(cfg.DSN)
if err != nil {
log.Fatalf("连接数据库失败: %v", err)
}
defer database.Close()
// 预建几条连接:PG 建连约 300ms,冷启动第一个页面会全吃掉
db.WarmUp(database, 6)
if err := db.Migrate(context.Background(), database); err != nil {
log.Fatalf("初始化数据库失败: %v", err)
}
// 自愈历史留档:早期回填的判据把「站点导出时给新建地点合成的 ZLOCATION 行」
// 当成了真机的原始 GPS,还误用了显示经度当原始经度。这里按 GCJ-02 往返关系重算。
if err := ingest.BackfillAllCoordSource(context.Background(), database); err != nil {
log.Printf("坐标来源留档自愈失败(不影响启动): %v", err)
}
adminUser := os.Getenv("ROND_ADMIN_USER")
if adminUser == "" {
adminUser = "admin"
}
// 不再写死出厂口令:公网部署漏配 ROND_ADMIN_PASSWORD 时,admin/rond-admin
// 等于后台没上锁。改成生成随机口令并在启动日志里打印一次。
// 取自环境变量的那份**不打日志**,免得口令被日志系统长期留存。
adminPass := os.Getenv("ROND_ADMIN_PASSWORD")
generatedPass := false
if adminPass == "" {
var err error
if adminPass, err = auth.RandomPassword(16); err != nil {
log.Fatalf("生成初始口令失败: %v", err)
}
generatedPass = true
}
store := &auth.Store{DB: database, Secret: []byte(cfg.SecretKey), Days: cfg.SessionDays}
if created, err := store.EnsureDefaultUser(context.Background(), adminUser, adminPass); err != nil {
log.Fatalf("初始化管理员失败: %v", err)
} else if created {
if generatedPass {
log.Printf("已创建初始管理员:%s / %s(随机生成,只打印这一次;登录后请立即修改)", adminUser, adminPass)
} else {
log.Printf("已创建初始管理员:%s(口令取自 ROND_ADMIN_PASSWORD)", adminUser)
}
}
srv, err := web.New(cfg, database)
if err != nil {
log.Fatalf("初始化服务失败: %v", err)
}
httpSrv := &http.Server{
Addr: cfg.Listen,
Handler: srv.Handler(),
ReadHeaderTimeout: 15 * time.Second,
WriteTimeout: 120 * time.Second,
IdleTimeout: 90 * time.Second,
}
go func() {
log.Printf("足迹站点已启动:http://localhost%s(后台 /admin)", cfg.Listen)
if err := httpSrv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("服务异常退出: %v", err)
}
}()
stop := make(chan os.Signal, 1)
signal.Notify(stop, os.Interrupt, syscall.SIGTERM)
<-stop
ctx, cancel := context.WithTimeout(context.Background(), 8*time.Second)
defer cancel()
_ = httpSrv.Shutdown(ctx)
log.Println("已退出")
}