feat(v2): T-309 SQLite fall-event persistence (mattn/go-sqlite3)

Persist confirmed fall events to a local SQLite DB under artifacts/:
camera host/port/channel + event id/track/source/config version/state/
latency/screenshot path/confirmed-at. Schema stores no password (guarded
by test). Wire openRecorder/saveRecord into the alerts loop; failures are
non-fatal. Re-applied the SILVER-POSE vendored os_windows.go patch that
go mod vendor reverted.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
ila
2026-07-23 22:15:55 +08:00
co-authored by Claude Opus 4.8
parent 4b7dfc2db6
commit 6c779aafb4
62 changed files with 292138 additions and 32 deletions
+36
View File
@@ -20,6 +20,7 @@ import (
"silverpose/v2/internal/monitor"
"silverpose/v2/internal/source"
"silverpose/v2/internal/spike"
"silverpose/v2/internal/store"
"silverpose/v2/internal/ui"
)
@@ -129,6 +130,10 @@ func (controller *controller) run(ctx context.Context, cancel func(), runID uint
controller.window.ReportIssue("无法启动监控管线")
return
}
recorder := openRecorder(cfg)
if recorder != nil {
defer recorder.Close()
}
go monitored.Run(ctx)
updates, alerts := monitored.Updates(), monitored.Alerts()
for updates != nil || alerts != nil {
@@ -145,10 +150,41 @@ func (controller *controller) run(ctx context.Context, cancel func(), runID uint
continue
}
controller.window.PresentAlert(record)
saveRecord(recorder, cfg, record)
}
}
}
// openRecorder opens the local SQLite fall database under the event directory.
// A failure is logged and monitoring continues without persistence.
func openRecorder(cfg config.Config) *store.Store {
if err := os.MkdirAll(cfg.Artifacts.EventDirectory, 0o755); err != nil {
log.Printf("创建事件目录失败:%v", err)
return nil
}
recorder, err := store.Open(filepath.Join(cfg.Artifacts.EventDirectory, "fall-events.db"))
if err != nil {
log.Printf("打开摔倒数据库失败:%v", err)
return nil
}
return recorder
}
func saveRecord(recorder *store.Store, cfg config.Config, record alert.Record) {
if recorder == nil {
return
}
if err := recorder.Save(store.Record{
EventID: record.Event.EventID, TrackID: record.Event.TrackID, SourceID: cfg.Source.ID,
CameraHost: cfg.Source.Host, CameraPort: cfg.Source.Port, CameraChannel: cfg.Source.Channel,
ConfigVersion: record.Event.ConfigVersion, State: fmt.Sprint(record.Event.State),
LatencySeconds: record.Event.LatencySeconds, ScreenshotPath: record.ScreenshotPath,
ConfirmedAtUTC: record.CreatedAtUTC.UTC().Format(time.RFC3339),
}); err != nil {
log.Printf("写入摔倒记录失败:%v", err)
}
}
func validateStartup(cfg config.Config) error {
for _, path := range []string{cfg.Model.ONNXPath, cfg.Model.ONNXRuntimeDLLPath, cfg.Tools.FFmpegPath, cfg.Tools.FFprobePath} {
info, err := os.Stat(path)